17

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

  • 2
    How about buffering and removing the old polygon!? buffer can be used with positive and negative values! – Farid Cheraghi Nov 10 '15 at 13:01
  • The question is about arcpy tool existing, not about how to resize a polygon. – Mr. Che Nov 10 '15 at 13:04
  • Your title, question, and comment seem to be at odds with each other. If the provided duplicate questions do not answer your question, could you please edit your question to clarify what you are after? – Aaron Nov 10 '15 at 13:30
  • 1
    @Mr.Che Buffer tool can be used in python scripting through arcpy.Buffer_analysis(...) – Farid Cheraghi Nov 10 '15 at 13:46
  • This is super! How can I update every feature class by a number in a table rather than scale all features by 0.5 for example? Thanks – user1655130 Mar 15 '16 at 14:28
27
+200

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.'

  • 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". – Mr. 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? – Mr. Che Nov 20 '15 at 4: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 9: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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.