Python Blog - Page 2

Showing results for 
Show  only  | Search instead for 
Did you mean: 

Other Boards in This Place

Latest Activity

(162 Posts)
MVP Notable Contributor

Bounding containers, split, transect lines.  So many things you can do with poly* features.


0 0 385
MVP Notable Contributor

Tools for working with tables not available in existing tools.


0 0 547
MVP Notable Contributor

What's the Point?  Lots of tools for working with them.  Several normally restricted to an Advanced License.


1 3 477
MVP Esteemed Contributor

Point in polygon. There are two basic methods with numerous variants.  One or the other in some form forms the foundation of spatial queries.


1 0 3,326
MVP Esteemed Contributor


The visual guide.

You can do it from within ArcGIS Pro.


5 9 1,188
MVP Esteemed Contributor

It's that time!

And don't forget, this is your first step

ArcGIS Pro 2.5 system requirements—ArcGIS Pro | Documentation 

Then check your specs.

Check your computer's ability to run ArcGIS Pro 2.5.

Check out Kory Kramer‌'s blog post for highlights and implementations 

Check out the highlights link in the help files....

What's new in ArcGIS Pro 2.5—ArcGIS Pro | Documentation 

Bookmark the help topic main links and you are ready.


4 5 612
MVP Esteemed Contributor


Probably one of the first things you did in a GIS class.

Select all the fast food outlets that are within 1 mile/km of a school. 

To the rescue... a little buffer, a selectorama, intersect, spatial join... whatever.  At some stage you have used buffers for spatial delineation either by creating one as a feature layer, or virtually when you select things within-a-distance-of.

So initially I thought I would just show the geometry created by buffering a simple shape or two to show the point densification on the outward corners.  In the old days a buffer around a point was represented by a 36-sided circle, if you like, an n-gon.  Pretty good if the buffer was small, but seriously lame when the buffer radius was very large.  Crrrrankout a buffer on a shapefile to see what I mean.  Many a line of code was written to approximate a true circle.  Now you can.

But what about a buffer?  The outward corners have that circular appearance, maintaining the offset radius (aka the buffer size) from the line work.  One would expect teeny-tiny circular arcs to appear in the geometry representation.  At worse, perhaps an n-gon representation of the arc.

Not so.  Here is a square green polygon being shown in Spyder with the arcpy's new svg display.  Pretty cool, but I like my numpy array version better (in red)

Geometry representationGeometry coordinates

p_0  # ---- arcpy.Polygon

Pretty standard, lots of extra information in the

geometry, but when you get to it, it is the 

coordinates to the right we are interested in.

To make things a bit easier on the eyes, I

subtracted the origin of the view space from the

x, y values.

[<Point (300010.0, 5000000.0, #, #)>,
<Point (300010.0, 5000010.0, #, #)>,
<Point (300020.0, 5000010.0, #, #)>,
<Point (300020.0, 5000000.0, #, #)>,
<Point (300010.0, 5000000.0, #, #)>]>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Shift the coordinates to the origin

coords = [(i.X - 300000, i.Y - 5000000)
for i in p_0[0]]

[(10.0, 0.0),
(10.0, 10.0),
(20.0, 10.0),
(20.0, 0.0),
(10.0, 0.0)]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

p0  # ---- a numpy Geo array of the above

Geo([[10., 0.],
[10., 10.],
[20., 10.],
[20., 0.],
[10., 0.]])‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In both cases, the coordinates are held in an array of some kind as in the above. 

In the example p_0 is the arcpy.Polygon and p_0[0] is the slice of the first object within... an arcpy.Array of arcpy.Point objects.

So what do the buffer coordinates look like?

Buffer coordinatesCommentary

arcpy.Polygon buffer coordinates

b_0 = p_0.buffer(1)
b_0[0] # get the array
[<Point (300020.0449000001, 4999999.001, #, #)>,
<Point (300020.0, 4999999.0, #, #)>,
<Point (300010.0449000001, 4999999.000499999, #, #)>,
<Point (300010.0, 4999999.000600001, #, #)>,
<Point (300009.0, 5000000.0, #, #)>,
<Point (300009.0, 5000010.0, #, #)>,
<Point (300010.0, 5000011.0, #, #)>,
<Point (300020.0, 5000011.0, #, #)>,
<Point (300021.0, 5000010.0, #, #)>,
<Point (300021.0, 5000000.0, #, #)>,
<Point (300020.0449000001, 4999999.001, #, #)>]>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Where are all the points?  I bet you 

can recognize some, but there is no

n-gon representation.

arcpy's __geo_interface__

{'type': 'MultiPolygon',
[[[(300020.0449000001, 4999999.001),
(300020.0, 4999999.0),
(300010.0449000001, 4999999.000499999),
(300010.0, 4999999.000600001),
(300009.0, 5000000.0),
(300009.0, 5000010.0),
(300010.0, 5000011.0),
(300020.0, 5000011.0),
(300021.0, 5000010.0),
(300021.0, 5000000.0),
(300020.0449000001, 4999999.001)]]]}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Well! No trace of those extra points.

Just a few lame ones with a teeny

offset from the ones making up the

offset lines.

What gives?  There is nothing

visible in the documentation of by examining the existing methods or

properties by conventional methods.

{"c":[ [300009,5000000],

JSON to the rescue?  Everyone loves those curly bracket things.

A bit more information like the curve ring thing.  But still no extra groups of points.

b_0svg = b_0.__getSVG__()

'<path fill-rule="evenodd"
fill="#66cc99" stroke="#555555"
stroke-width="2.0" opacity="0.6"
M 300020.0449000001,4999999.001
L 300020,4999999
L 300010.0449000001,4999999.000499999
... snip
L 300009,5000000
L 300009,5000010
L 300009.0048088113,5000010.098019366
... snip
L 300010,5000011
L 300020,5000011
L 300020.09801936575,5000010.9951911885
L 300021,5000010
L 300021,5000000
L 300020.9954546047,4999999.904777056
... snip
L 300020.0449000001,4999999.001 z" />'

# ---- now take the same corner as an array

print(arc) ID degrees
[[20. , 11. ], 0
[20.09801937, 10.99519119], 1
[20.19509527, 10.98079709], 2 78.75
[20.29029264, 10.95695635], 3
[20.38269452, 10.92389861], 4 67.5
[20.47141085, 10.8819423 ], 5
[20.55558711, 10.83149154], 6 56.3
[20.63441247, 10.7730323 ], 7
[20.70712768, 10.70712768], 8 45.0
[20.7730323 , 10.63441247], 9
[20.83149154, 10.55558711], 10
[20.8819423 , 10.47141085], 11
[20.92389861, 10.38269452], 12
[20.95695635, 10.29029264], 13
[20.98079709, 10.19509527], 14 11.25
[20.99519119, 10.09801937], 15
[21. , 10. ]]) 16‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

SVG to the rescue!

Huge swaths of coordinates for each


Miraculously, they have appeared by

some hidden magic that we are not made party to

So, What do the corners look like

As expected.  I created the rounded corners for the numpy-based Geo array.  In the example below, I plotted the SVG corner points and the Geo array points.  An angle of about 5 degrees (give or take) is used by both.  Smooth enough and you can account for area calculations if you know number of outward buffer corners, the total angular passage and the number n-gon shape.  Or just use arcpy's Polygon methods to get the area.

To leave you with some final thoughts. 

You can buffer 

  • with a uniform distance around the shape
  • an offset buffer, where the shape is expanded parallel to the forming lines (see above)
  • a chordal buffer, (Buffer Geometry figure)
  • buffer by increasing the area or perimeter (for polygons obviously).

This is an example of the last which is based on the offset buffer, but using area as the parameter.

So remember what goes into the buffer and that not all information that you would expect to see is shown in all forms of geometry representation.

0 0 339
MVP Esteemed Contributor

Geometry ...



I really think it is a bit of overkill to create a FeatureClass just to see some geometry I have created or changed.

As of ArcGIS Pro 2.5, you can view a geometry object inside of Jupyter notebooks, Jupyter-lab or any other thing that supports SVG.  This missive shows how to view geometry from arcpy and also how to deal with geometry without having to go to arcpy to do it.

If you don't create or edit geometry with python, you can go now.


Start with geometry

Begin with some polygons.

In [ 1]: polys
In [or]: print(polys)
In [or]: polys.__repr__()

[<Polygon object at 0x25941289d30[0x2594152f288]>,<Polygon object at 0x25941289cf8[0x259414a4bc0]>,
<Polygon object at 0x25941289c88[0x259414a4b98]>,<Polygon object at 0x25941289cc0[0x25940a241c0]>,
<Polygon object at 0x25941289c18[0x25940a240d0]>]

As In [ 1]: shows, you can get a 'representation' of the python geometry from within python.  It tells you that it is a polygon and then gives you the memory stuff (I presume).  The former you probably already knew and the latter you probably don't care about.  Pretty useless. So the quest continues.

Every object in python has a string representation, so lets try there.

In [ 2]: str(p_0)
In [or]: p_0.__str__()

'<geoprocessing describe geometry object object at 0x000002594152F288>'

Heart be still! In [ 2]: is even more cryptic, but the memory location idea was spot on.

Let's see what 'dir' reveals.  I have snipped out a lot of stuff, and highlighted the more useful formats.  Make sure you explore the various geometry classes on your own.

In [3]: dir(p_0)

['JSON', 'WKB', 'WKT', ... snip ...'__geo_interface__',
'__getSVG__', ... the new addition ....snip ...
'_fromGeoJson', ... snip ... '_repr_svg_', ... snip ...
...all the other properties and methods

Interesting.. maybe if a geometry contains more than one thing, slicing might real more.

In [4]: p_0[0]

    [<Point (300010.0, 5000010.0, #, #)>, <Point (300010.0, 5000000.0, #, #)>,
    ... snip ...
          ... snip ...
    <Point (300002.0, 5000008.0, #, #)>, <Point (300001.0, 5000009.0, #, #)>,
    <Point (300001.0, 5000008.0, #, #)>, <Point (300002.0, 5000008.0, #, #)>]>

Perfect!  Polygons are made up of Array objects which are made up of Point objects.  Parts of arrays are separated by None, so you can have the polygons with multiple parts and/or holes in the parts.

Your education has been confirmed.


What is that __getSVG__ thing?

In [5]: p_0.__getSVG__()

'<path fill-rule="evenodd"
d=" M 300010,5000010 L 300010,5000000 L 300001.5,5000001.5 L 300000,5000010 L 300010,5000010
    M 300003,5000009 L 300003,5000003 L 300009,5000003 L 300009,5000009 L 300003,5000009
    M 300002,5000007 L 300001,5000007 L 300002,5000005 L 300002,5000007
    M 300002,5000008 L 300001,5000009 L 300001,5000008 L 300002,5000008

Well they sure look like coordinates.  Time to go off and research SVG construction.

since __repr__ was revealing perhaps _repr_svg_ will be too.

In [6]: p_0._repr_svg_()

'<svg xmlns=""
width="100.0" height="100.0"
viewBox="299999.6 4999999.6 10.800000000046566 10.800000000745058"
preserveAspectRatio="xMinYMin meet">
<g transform="matrix(1,0,0,-1,0,10000010.0)">
<path fill-rule="evenodd"
d=" M 300010,5000010 L 300010,5000000 L 300001.5,5000001.5 L 300000,5000010 L 300010,5000010
    M 300003,5000009 L 300003,5000003 L 300009,5000003 L 300009,5000009 L 300003,5000009
    M 300002,5000007 L 300001,5000007 L 300002,5000005 L 300002,5000007
    M 300002,5000008 L 300001,5000009 L 300001,5000008 L 300002,5000008


On to the Display

 Inside of Spyder, which uses an IPython console,

I used my handy function for representing a numpy-based array as geometry... which I was working with. 

You get a quick peek at what it looks like. 

The alternative???

  •  use my other handy functions and make a featureclass,
  • crank up Pro and
  • add it to a map

Saving the SVG

Pretty easy.  Just right-click on it and you can save the SVG as an image or a *.svg file for use in such programs as Word.

The Code

I put it in a gist at...

 svg display for numpy geometries

If you are interested in alternatives and/or supplements to the various geometry packages, I have been working on

 ... npGeom ... which provides the basis for my

... Free Tools ...collection of tools normally restricted to the Advanced or Standard license for ArcGIS Pro.

So if you like geometry, between arcpy, python, numpy and the various display environments, you can find a match for the job at hand.

Try your hand with a triangle and a square...

t = np.array([[ 0.00,  0.00], [ 0.50,  1.00], [ 1.00,  0.00], [ 0.00,  0.00]])
r = np.array([[ 0.00, 2.00], [ 0.00, 1.00], [ 1.00, 1.00],
[ 1.00, 2.00], [ 0.00, 2.00]])

4 0 967
MVP Honored Contributor

It is possible to create an ArcMap Python Addin tool that simultaneously captures clicked map coordinates stamped with a user name and date time in an ArcMap layer displayed on a map and collect them into a table in a Microsoft Access database housed on a shared network drive.  The tool outlined in this blog only keeps track of the last coordinate clicked by each user in the Access coordinate table with the expectation that these records would be used like a clipboard by custom Access forms that control the creation or editing of records within Access.  However, the tool code presented here could be modified to directly update any Access table from within ArcMap if prompts are added to the tool that let the user choose the Access records they want to create or edit as a result of each mouse click.

The pictures below shows the result of using the tool to capture the location of two different STOP markings on a road.  The table records in ArcMap and Access match completely after simply activating the tool and clicking on each marking.

The tool only activates when the left mouse button is clicked within a map and the Alt, Ctrl and Shift keys are not being pressed.  I created the Access tblGIS_COORDINATE table manually, but the ArcMap GIS_COORDINATE feature class will automatically be created by the tool if it doesn't exist in the default database and be added to the map as a layer and made visible if it is not currently a layer in the map or visible when the mouse is clicked.  The tool ensures that the cache is cleared if the GIS_COORDINATE table view is open so that it will refresh to match the point location on the map and you may have to make Access the currently active program to see its table refresh if it is open.

Technical Notes:

To build this tool you should download the addin_assistant tool from  If you are running Python 3 or higher you will have to fix one line of code in the file created by the addin assistant by changing it from:

print archive_file



to build the tool use the wizard to first create a toolbar and then create a tool under it.

For the tool icon I used the EditingRemoteEditSynchronize32.png file in the C:\Program Files (x86)\ArcGIS\Desktop10.6\bin\Icons directory, but you are welcome to design your own.


To install pyodbc you can do the following:

  1. pyodbc list here is a list of whl files. Download the right one for you.
  2. Open cmd as administrator and go to the directory containing the file.
  3. Run pip install pyodbc-xxxxx.whl.

If you get an error about the odbcji32.dll required to use the {Microsoft Access Driver (*.mdb, *.accdb)} you can follow the instructions at Unable to load odbcji32.dll (MS Access ODBC driver) 

The tool captures coordinates in both the State Plane Spatial Reference preferred by my jurisdiction and in the GCS WGS 1984 Spatial Reference used by Google maps.  You should change the State Plane Spatial Reference to your own preferred Spatial Reference.  You can find the WKID of your preferred Spatial Reference by adding a layer that uses that Spatial Reference to your map and opening the Data Frame Properties dialog, choosing the Coordinate System tab and choosing the Spatial Reference under the Layers group at the bottom of the list.

I am not an ArcGIS Pro user, so I have not explored whether or not a similar addin can be built for use in ArcGIS Pro, but the development of a tool for Pro shouldn't affect the pyodbc code that makes communication with Microsoft Access possible.

I hope many of you will find this useful and intriguing.  I am also open to suggestions for other ways that pyodbc and python code might be used to create tools that may integrate Access and ArcMap more fully.

import arcpy
import pythonaddins
import getpass
from datetime import datetime
import pyodbc

class CaptureCoordinateTool(object):
"""Implementation for Capture_Coordinate2_addin.tool (Tool)"""
def __init__(self):
self.enabled = True
self.shape = "NONE" # Can set to "Line", "Circle" or "Rectangle" for interactive shape drawing and to activate the onLine/Polygon/Circle event sinks.
def onMouseDownMap(self, x, y, button, shift):

# Determine if the button and shift states are correct for activating the tool
if button != 1 or shift != 0: # The left mouse button was not pressed or the Alt, Ctrl and/or Shift keys were pressed
pass # Do default ArcMap behavior and exit

else: # The left mouse button was pressed and the Alt, Ctrl and Shift keys were not pressed

# Get information about the settings of the map clicked
mxd = arcpy.mapping.MapDocument("CURRENT") # get the current map where the mouse was pressed
df = arcpy.mapping.ListDataFrames(mxd)[0] # Assume the first data frame in the map was clicked
sr = df.spatialReference # get the spatial reference of the data frame

if sr.factoryCode != 2230 and sr.factoryCode != 4326: # Spatial Reference is invalid if it is not 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet) or 4326 (GCS_WGS_1984)
message = "Invalid Spatial Reference " + str(sr.factoryCode) + " - " + + " detected.\nPlease set the Spatial Reference to\n2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet)\nor 4326 (GSC_WGS_1984)"
pythonaddins.MessageBox(message, "Map Spatial Reference Is Invalid") # Display a message alerting user that the map has an invalid spatial reference and exit

else: # Spatial Reference is either 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet) or 4326 (GCS_WGS_1984) and therefore valid
# Get the GIS_COORDINATE layer and if necessary Create the feature class or add the layer to the map
user_name = getpass.getuser() # get the login name of the user
out_path = "C:\Users\{}\Documents\ArcGIS\Default.gdb".format(user_name) # ArcMap Default geodartbase
out_name = "GIS_COORDINATE" # FC name
coordinate_fc = out_path + "\\" + out_name # Full path and name of FC
geometry_type = "POINT" # Features will be points
template = ""
has_m = "DISABLED"
has_z = "DISABLED"
spatial_ref = arcpy.SpatialReference(2230) # Set Spatial Reference to 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet) preferred by Riverside County
if not arcpy.Exists(coordinate_fc):
# Create an FC for displaying the clicked point in the preferred spatial reference and add fields to match the Access table schema
arcpy.CreateFeatureclass_management(out_path, out_name, geometry_type, template, has_m, has_z, spatial_ref)
arcpy.AddField_management(coordinate_fc, "USER_NAME", "TEXT", field_length=50)
arcpy.AddField_management(coordinate_fc, "LONGITUDE", "DOUBLE")
arcpy.AddField_management(coordinate_fc, "LATITUDE", "DOUBLE")
arcpy.AddField_management(coordinate_fc, "X_COORDINATE", "DOUBLE")
arcpy.AddField_management(coordinate_fc, "Y_COORDINATE", "DOUBLE")
arcpy.AddField_management(coordinate_fc, "COORDINATE_DATE", "DATE")
coordinate_lyr = None # Create an unassigned variable for a layer that will display the coordinate
for lyr in arcpy.mapping.ListLayers(mxd, "", df): # Check all existing layers in the dataframe clicked
if lyr.dataSource.lower() == coordinate_fc.lower(): # Check if any layer has the coordinate fc as its datasource
coordinate_lyr = lyr # set the coordinate layer variable to the map layer that has the coordinate feature class
coordinate_lyr.visible = True # make sure the layer is visible
if coordinate_lyr == None: # Check if no layer was found in the map
arcpy.MakeFeatureLayer_management(coordinate_fc, out_name) # Make a layer from the map, which should automatically be added to the map
for lyr in arcpy.mapping.ListLayers(mxd, "", df): # Recheck all existing layers in the dataframe clicked
if lyr.dataSource.lower() == coordinate_fc.lower(): # Find the created layer that has the coordinate fc as its datasource
coordinate_lyr = lyr # set the coordinate layer variable to the map layer that has the coordinate feature class
coordinate_lyr.visible = True # make sure the layer is visible

# Capture date for the point geometries and coordinates of both spatial references, the user name and the date time when the data was captured
x_stateplane, y_stateplane, longitude, latitude, state_plane_PtGeom, wgs_1984_PtGeom = 0, 0, 0, 0, None, None # Initialize variables for state plain and wgs 1984 coordinates and points
point = arcpy.Point() # Create a point object
point.X, point.Y = x, y # Set the x, y of the point object to the coordinates clicked
pointGeom = arcpy.PointGeometry(point, sr) # create a PointGeometry based on the point coordinates and spatial reference of the map click

if sr.factoryCode == 2230: # Spatial Reference is 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet)
state_plane_PtGeom = pointGeom # set state_plane_PtGeom to pointGeom clicked
x_stateplane, y_stateplane = x, y # x_stateplane and y_stateplane to x and y clicked
srOut = arcpy.SpatialReference(4326) # Create a 4326 (GCS_WGS_1984) Spatial Reference
wgs_1984_PtGeom = pointGeom.projectAs(srOut) # Project the point clicked to 4326 (GCS_WGS_1984) spatial reference
longitude, latitude = wgs_1984_PtGeom.centroid.X, wgs_1984_PtGeom.centroid.Y # set longitude and latitude to projected X and Y

elif sr.factoryCode == 4326: # Spatial Reference is 4326 (GCS_WGS_1984)
wgs_1984_PtGeom = pointGeom # set wgs_1984_PtGeom to pointGeom clicked
longitude, latitude = x, y # set longitude to latitude to x and y clicked
srOut = arcpy.SpatialReference(2230) # Create a 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet) Spatial Reference
state_plane_PtGeom = pointGeom.projectAs(srOut) # Project the point clicked to 2230 (NAD_1983_StatePlane_California_VI_FIPS_0406_Feet) spatial reference
x_stateplane, y_stateplane = state_plane_PtGeom.centroid.X, state_plane_PtGeom.centroid.Y # set x_stateplane and y_stateplane to projected X and Y

dtnow = # Capture the current datetime

# Cutput the captured data
conn_str = (
r'DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};'
) # set up a connection string for connecting to an Access accdb database
cnxn = pyodbc.connect(conn_str) # connect to the Access accdb database
crsr = cnxn.cursor() # create a cursor from the connection
crsr.execute("SELECT USER_NAME FROM tblGIS_COORDINATE WHERE USER_NAME = ?", user_name) # Execute a query to find any records that match the user_name that clicked the point
row = crsr.fetchone() # fetch the first record (there should only be none or one record)
if row: # If a row was found update the record with the coordinates and date time of the user click
crsr.execute("UPDATE tblGIS_COORDINATE SET LONGITUDE = ?, LATITUDE = ?, X_COORDINATE = ?, Y_COORDINATE = ?, COORDINATE_DATE = ? WHERE USER_NAME = ?", longitude, latitude, x_stateplane, y_stateplane, dtnow, user_name)
else: # If no row was found insert a record for the current user_name with the coordinates and date time of the user click
crsr.execute("INSERT INTO tblGIS_COORDINATE(USER_NAME, LONGITUDE, LATITUDE, X_COORDINATE, Y_COORDINATE, COORDINATE_DATE) values (?, ?, ?, ?, ?, ?)", user_name, longitude, latitude, x_stateplane, y_stateplane, dtnow)
crsr.close() # close the accdb cursor
cnxn.close() # close the accdb connection

arcpy.SelectLayerByAttribute_management(, "CLEAR_SELECTION") # Make sure no records are selected in the layer displaying the clicked coordinate
fields = ['SHAPE@', 'USER_NAME', 'LONGITUDE', 'LATITUDE', 'X_COORDINATE', 'Y_COORDINATE', 'COORDINATE_DATE'] # create a list of fields for the layer to be updated or inserted
count = 0 # Create a counter to determine if a record exists already or needs to be inserted
with arcpy.da.UpdateCursor(, fields) as cursor: # process an update cursor on the layer
for row in cursor: # iterate through all records
if row[1] == user_name: # only update a record if it matches the user_name of the the user that clicked the map
row[0] = state_plane_PtGeom # create the point shape using the point geomtery with preferred spatial reference
row[2] = longitude # update the longitude
row[3] = latitude # update the latitude
row[4] = x_stateplane # update the x coordinate in the preferred spatial reference
row[5] = y_stateplane # update the y coordinate in the preferred spatial reference
row[6] = dtnow # update the coordinatte date to the datetime of the map click
cursor.updateRow(row) # post the update to the row
count += 1 # increment the counter to so that another record will not be inserted
if count == 0: # determine if no feature with the user name exists
cursor = arcpy.da.InsertCursor(, fields) # if none exists create an insert cursor
cursor.insertRow((state_plane_PtGeom, user_name, longitude, latitude, x_stateplane, y_stateplane, dtnow)) # insert a new feature for the user with the coordinate and date time of the click
del cursor # delete the insert cursor
arcpy.RefreshActiveView() # refresh the active dataframe to show the point located at the position the user just clicked

except Exception as e: # catch any errors generated by the tool
pythonaddins.MessageBox("An Error Occurred Attempting to Capture a Coordinate\n" + str(e), "Error Capturing Coordinate") # Display a message showing that an error occurred executing the tool

1 2 440
MVP Esteemed Contributor


Details as I go.  Everything is there to assist you from initial project thought to final application.

Right now... just the pics and a few tips.

An attachment of the first image, as well, if you want to explore in more detail.


Spyder 4's current version and changelog can be tracked at...

Spyder changelog on GitHub 

Theme choices

There are a variety of ways to layout and style the IDE.  A full dark theme is shown to the right .

Or you can split the themes and have different ones for  the editor and console.  The image below shows a lighter theme for the Variable explorer and the file explorer.

Separate (floating) or in-pane graphics available using direct access to Matplotlib.

Preference options

Graphics options

There is a new Plots window, or you can set your graphics to automatic to get a separate matplotlib graph window.  From there you can interactively alter the graphic to suit your needs.

You will also note, that svg inline graphics are supported.  I wrote a function to display numpy arrays representing geometry objects to get a quick preview without the need to create a featureclass.

File/project and script navigation

Help documentation and presentation

Help is everywhere. 

The example to the right shows what a function docstring looks link in the console and in the help tab. 

You can choose between coding styles within the preferences.

The numpydoc style used by packages like scipy, matplotlib, pandas to name a few, is shown below for comparison.

Editing tips


I hate scrolling, so when you get an error, click on the line number.  If it is in an imported script, you can even click on the script name to go there.

The object explorer can be used to retrieve information for objects.  This is useful for documentation purposes.

Finding stuff

When your package gets large and you are trying to locate something... Find is your friend.

A quick click and you are there to make edits, copy or just read.

Kite can be installed as well

Kite - AI Autocomplete and Docs for Python 

5 6 2,718