18

I am writing a python script for ArcGIS 10.3. I know about Scale tool in ArcGIS interface but I can't find such arcpy command. It exists?

As you can see on the picture the Scale tool works different than Buffer tool - it changes the form of original polygon. So the question is:

Can I use Scale tool (available from ArcGIS interface) using arcpy?

enter image description here

Comrade Che
  • 7,091
  • 27
  • 58

1 Answers1

32

I'm not aware of anything in the arcpy API that will do the scaling for you, but writing a function to do so would be relatively simple.

The code below does the scaling for 2D features, and doesn't take into account M or Z values:

import arcpy
import math

def scale_geom(geom, scale, reference=None):
    """Returns geom scaled to scale %"""
    if geom is None: return None
    if reference is None:
        # we'll use the centroid if no reference point is given
        reference = geom.centroid

    refgeom = arcpy.PointGeometry(reference)
    newparts = []
    for pind in range(geom.partCount):
        part = geom.getPart(pind)
        newpart = []
        for ptind in range(part.count):
            apnt = part.getObject(ptind)
            if apnt is None:
                # polygon boundaries and holes are all returned in the same part.
                # A null point separates each ring, so just pass it on to
                # preserve the holes.
                newpart.append(apnt)
                continue
            bdist = refgeom.distanceTo(apnt)

            bpnt = arcpy.Point(reference.X + bdist, reference.Y)
            adist = refgeom.distanceTo(bpnt)
            cdist = arcpy.PointGeometry(apnt).distanceTo(bpnt)

            # Law of Cosines, angle of C given lengths of a, b and c
            angle = math.acos((adist**2 + bdist**2 - cdist**2) / (2 * adist * bdist))

            scaledist = bdist * scale

            # If the point is below the reference point then our angle
            # is actually negative
            if apnt.Y < reference.Y: angle = angle * -1

            # Create a new point that is scaledist from the origin 
            # along the x axis. Rotate that point the same amount 
            # as the original then translate it to the reference point
            scalex = scaledist * math.cos(angle) + reference.X
            scaley = scaledist * math.sin(angle) + reference.Y

            newpart.append(arcpy.Point(scalex, scaley))
        newparts.append(newpart)

    return arcpy.Geometry(geom.type, arcpy.Array(newparts), geom.spatialReference)

You can call it with a geometry object, a scale factor (1 = same size, 0.5 = half size, 5 = 5 times as large, etc.), and an optional reference point:

scale_geom(some_geom, 1.5)

Use this in conjunction with cursors to scale an entire feature class, assuming the destination feature class already exists:

incur = arcpy.da.SearchCursor('some_folder/a_fgdb.gdb/orig_fc', ['OID@','SHAPE@'])
outcur = arcpy.da.InsertCursor('some_folder/a_fgdb.gdb/dest_fc', ['SHAPE@'])

for row in incur:
    # Scale each feature by 0.5 and insert into dest_fc
    outcur.insertRow([scale_geom(row[1], 0.5)])
del incur
del outcur

edit: here's an example using an approximation of your test geometry, for 0.5 and 5 times: enter image description here

Also tested with multi-ring polygons (holes)! enter image description here

An explanation, as requested:

scale_geom takes a single polygon and loops through each vertex, measuring the distance from it to a reference point (by default, the centroid of the polygon).
That distance is then scaled by the scale given to create the new 'scaled' vertex.

The scaling is done by essentially drawing a line at the scaled length from the reference point through the original vertex, with the end of the line becoming the scaled vertex.
The angle and rotation stuff is there because it's more straight forward to calculate the position of the end of the line along a single axis and then rotate it 'into place.'

Evil Genius
  • 6,289
  • 2
  • 27
  • 40
  • 1
    I tested this script and it works fine. You are damn Genius! =) Thx a lot. I will left this question unawarded so more people will see it in the "futured questions". – Comrade Che Nov 18 '15 at 10:37
  • 1
    I found that when I attempt to process a polygon with a hole - it results in a script crash in line bdist = refgeom.distanceTo(apnt). Can you test and fix that? – Comrade Che Nov 20 '15 at 04:15
  • @Mr.Che Oops, I forgot that ArcPy returns all rings of a polygon part in the same array. The rings are separated by null points. It's an easy fix, please see the edit. – Evil Genius Nov 20 '15 at 12:04
  • Hello. Is this possible to get a small explanation of how script works, I am bad at coding, and not getting all the lines, thus it is not working for me, please? – peter Jan 12 '17 at 09:14
  • @peter Sure, I've added a short explanation of what's going on. It's not meant to be a stand alone script, but something to be integrated into a script of your own. The bottom code snippet shows an example of how it could be used. – Evil Genius Jan 12 '17 at 12:28
  • Sorry for my very basic question as I am a bit ignorant with coding, I tried the code and it seems to work, it seems the codes adds polygons on top of each other and in one attribute table. Is it possible to save every try as a separate file as well? Thanks in advance. – peter Jan 16 '17 at 15:32
  • How can we use reference points for enlarging these polygons? Here, I guess it is working with calculation of Centriod. – peter Mar 27 '17 at 10:07
  • @peter Not sure I understand your question. If a reference isn't supplied it uses the centroid, but any arcpy.Point could be used. The scaling is done relative to that point. A reference point other than the centroid won't effect the size or shape, but resulting polygon would be in a different location. You could also call it an 'anchor point', I suppose. – Evil Genius Mar 27 '17 at 11:15
  • @ Evil Genius: It works very well for round polygons or with compact polygons, however polygons with concave and convex shapes get enlarged not proportionally when used centeroid points. That is why, I would like to use reference point instead of centeroid, however, do not exactly know how to use it in the code. How can I use reference point instead of centeroid in above code? – peter Mar 27 '17 at 18:22
  • @peter It's an optional argument to scale_geom. You would use it in the form of scale_geom(some_geom, scale, ref_point) where ref_point is any arcpy.Point. – Evil Genius Mar 27 '17 at 18:44