├── .gitignore
├── .idea
└── esri-python-tools-master.iml
├── README.md
├── Utilities_ArcMap.pyt
├── Utilities_Pro.pyt
├── __init__.py
├── addins
└── definitionquery
│ ├── Images
│ ├── Filter-256.png
│ ├── Thumbs.db
│ └── clean_brush-512.png
│ ├── Install
│ ├── __init__.py
│ └── addins_addin.py
│ ├── README.txt
│ ├── addins.esriaddin
│ └── makeaddin.py
├── img
├── Signature.PNG
├── defquery_addin.gif
└── line_endpoint.PNG
├── lib
├── __init__.py
├── esri
│ ├── Attachments.py
│ ├── Extent.py
│ ├── Geodatabase.py
│ ├── Geometry.py
│ ├── MapDocument.py
│ ├── ProDataDrivenPages.py
│ ├── Raster.py
│ └── __init__.py
└── util
│ ├── File_Operations.py
│ ├── String.py
│ └── __init__.py
├── scripts
├── LabelRelatedRecords.py
├── Reproject.py
├── UpdateMXDMetadata.py
├── layer_selection.py
└── update_related_ids.py
└── tools
├── ClipTool.py
├── DataUpdater.py
├── ExtractAttachmentsTool.py
├── FTPMirrorTool.py
├── LineEndpointTool.py
├── PointElevationTool.py
├── PolygonCentroidTool.py
├── ReprojectTool.py
├── SitemapTool.py
└── __init__.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.mkv
3 | custom/
4 | *.xml
5 | *.orig
6 | /.vs/Python/v15
7 | /Python.pyproj
8 | .vs/
9 | Python.sln
10 | *.pyproj
11 | *.sln
12 |
--------------------------------------------------------------------------------
/.idea/esri-python-tools-master.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcGIS Desktop Python Tools
2 |
3 | A collection of custom Esri toolboxes written in python.
4 |
5 | ## Add-ins
6 |
7 | Python add-ins for ArcMap.
8 |
9 | ## Definition Query
10 |
11 | Quickly apply a definition query to selected layers using selected feature object id's
12 |
13 | 
14 |
15 | ### Installation
16 |
17 | 1. Install the .addin file
18 | 2. Open ArcMap and add the toolbar "Faribault"
19 |
20 | ### Usage
21 |
22 | 1. Select some features
23 | 2. Select the layer in the TOC
24 | 3. Apply the definition query using the filter button
25 |
26 | ## Utilities toolbox
27 |
28 | ArcGIS Utilities
29 |
30 | ### Generate Site Maps and CSV
31 |
32 | A custom data driven pages tool used for a variety of purposes.
33 |
34 | - Optionally buffers selected feature or features
35 | - Selects features that intersect the buffer
36 | - Exports map of all features
37 | - Optionally exports individual data driven pages for each feature
38 | - Exports a csv file with the selected features
39 |
40 | ### Clip geodatabase
41 |
42 | This tool clips each layer in a geodatabase and projects the data into an output database using a specified projection file.
43 |
44 | ### Reproject geodatabase
45 |
46 | Reprojects an entire geodatabase using a specified projection file. Layers not in a dataset will be placed in a `_top` dataset.
47 |
48 | ### Polygon Centroid Tool
49 |
50 | - Copies a polygon feature class's centroid geometry into a new feature class
51 | - Adds field `Rel_OID` type `Long` which represents the object id of the polygon
52 |
53 | ### Line Endpoint Tool
54 |
55 | 
56 |
57 | - Copies the start and endpoint of lines into a new feature class
58 | - Adds field `related_oid` type `Long` which represents the object id of the line feature
59 | - Adds field `point_type` type `Text` which will either be value `START` or `END`
60 | - See
61 |
62 | ### Point Elevation Tool
63 |
64 | - Extracts elevation values at points and calculates them to a field
65 | - This may crash ArcMap with large data sets
66 |
67 | ### Extract attachments tool
68 |
69 | - Extracts attachments from a table and generates a unique file name for each file
70 | - Adds a new field to the table called `file_name` that is updated to the file's unique name
71 | - Requires fields `'DATA', 'ATT_NAME', 'ATTACHMENTID'`
72 |
73 | ## Scripts
74 |
75 | ### Update MXD Metadata
76 |
77 | `scripts/UpdateMXDMetadata.py`
78 |
79 | **Description:** Iterates through an mxd's layers and extracts data source metadata. Layer descriptions are updated to the latest abstract, purpose, and credits.
80 |
81 | Each metadata category is separated into a separate html paragraph using `` tags.
82 |
83 | **Purpose:** Useful to update metadata on an arcgis server rest endpoint, because html paragraphs are formatted on the rest endpoint and in JSON responses.
84 |
85 | **Usage:**
86 |
87 | Run the script inside an arcmap python console that you want to update.
88 |
89 | ### Label Related Records
90 |
91 | `scripts/LabelRelatedRecords.py`
92 |
93 | **Description:** Looks up related records and generates a label with selected field in a list-like format.
94 |
95 | **Usage:** See documented script
96 |
97 | ### Calculate Dynamic Expression Related Records
98 |
99 | **Description:** Calculates the dynamic expression for a layer and can be used to dynamically display related records text for data driven pages or feature labels
100 |
101 | **Usage (see script for detail)**
102 |
103 | - Change the FindLabel parameter to the id field of the main table
104 | - This id should be related to each of the related tables
105 | - Also change the first line of FindLabel. Set parameter equal to the FindLabel parameter
106 | - Change the lookup tables and assign their appropriate formatter function
107 |
--------------------------------------------------------------------------------
/Utilities_ArcMap.pyt:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | path = os.path.dirname(os.path.abspath(__file__))
4 | sys.path.append(path)
5 |
6 | from tools.SitemapTool import SiteMapGenerator
7 | from tools.ClipTool import Clip
8 | from tools.ReprojectTool import Reproject
9 | from tools.PolygonCentroidTool import PolygonCentroidToPoint
10 | from tools.LineEndpointTool import LineEndPoints
11 | from tools.PointElevationTool import PointElevations
12 | from tools.ExtractAttachmentsTool import ExtractAttachments
13 | from tools.FTPMirrorTool import FTPMirror
14 |
15 | class Toolbox(object):
16 | def __init__(self):
17 | self.label = 'ArcMapUtilities'
18 | self.alias = 'ArcMapUtilities'
19 | self.tools = [
20 | Clip,
21 | Reproject,
22 | SiteMapGenerator,
23 | PolygonCentroidToPoint,
24 | LineEndPoints,
25 | PointElevations,
26 | ExtractAttachments,
27 | FTPMirror,
28 | ]
29 |
--------------------------------------------------------------------------------
/Utilities_Pro.pyt:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | path = os.path.dirname(os.path.abspath(__file__))
4 | sys.path.append(path)
5 | from tools.DataUpdater import MultipleLayerUpdater
6 | from tools.ClipTool import Clip
7 | from tools.ReprojectTool import Reproject
8 | from tools.PolygonCentroidTool import PolygonCentroidToPoint
9 | from tools.LineEndpointTool import LineEndPoints
10 | from tools.PointElevationTool import PointElevations
11 | from tools.ExtractAttachmentsTool import ExtractAttachments
12 | from tools.FTPMirrorTool import FTPMirror
13 |
14 | class Toolbox(object):
15 | def __init__(self):
16 | self.label = 'Utilities'
17 | self.alias = 'Utilities'
18 | self.tools = [
19 | Clip,
20 | Reproject,
21 | MultipleLayerUpdater,
22 | PolygonCentroidToPoint,
23 | LineEndPoints,
24 | PointElevations,
25 | ExtractAttachments,
26 | FTPMirror,
27 | ]
28 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/__init__.py
--------------------------------------------------------------------------------
/addins/definitionquery/Images/Filter-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/addins/definitionquery/Images/Filter-256.png
--------------------------------------------------------------------------------
/addins/definitionquery/Images/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/addins/definitionquery/Images/Thumbs.db
--------------------------------------------------------------------------------
/addins/definitionquery/Images/clean_brush-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/addins/definitionquery/Images/clean_brush-512.png
--------------------------------------------------------------------------------
/addins/definitionquery/Install/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from sys import path
3 | from os.path import dirname
4 | cwd = dirname(__file__)
5 | print(cwd)
6 | path.append(cwd)
7 |
--------------------------------------------------------------------------------
/addins/definitionquery/Install/addins_addin.py:
--------------------------------------------------------------------------------
1 | import arcpy
2 | import pythonaddins
3 |
4 |
5 | def get_layer(layer):
6 |
7 | # we might be passed a layer object already
8 | if not isinstance(layer, basestring):
9 | return layer
10 |
11 | # always use current
12 | doc = arcpy.mapping.MapDocument('current')
13 | try:
14 | layer = [l for l in arcpy.mapping.ListLayers(doc) if l.name == layer][0]
15 | return layer
16 | except IndexError as e:
17 | raise Exception('Layer not found::{layer}'.format(layer=layer))
18 | return
19 |
20 | def create(layer):
21 | # layer = get_layer(layer)
22 |
23 | # check selection set
24 | ids = [str(id) for id in layer.getSelectionSet()]
25 | if not len(ids):
26 | print('No selections on current layer')
27 | return
28 |
29 | id_field = arcpy.Describe(layer).OIDFieldName
30 |
31 | # set definition query
32 | layer.definitionQuery = '{field} IN ({ids})'.format(field=id_field, ids=','.join(ids))
33 | arcpy.RefreshActiveView()
34 |
35 | def clear(layer):
36 | # layer = get_layer(layer)
37 | layer.definitionQuery = ''
38 | arcpy.RefreshActiveView()
39 |
40 |
41 | class applyDefButton(object):
42 | """Implementation for addins_addin.defButton (Button)"""
43 | def __init__(self):
44 | self.enabled = True
45 | self.checked = False
46 | def onClick(self):
47 | layer = pythonaddins.GetSelectedTOCLayerOrDataFrame()
48 | print(layer)
49 | create(layer)
50 |
51 | class clearDefinitions(object):
52 | """Implementation for addins_addin.clearDefs (Button)"""
53 | def __init__(self):
54 | self.enabled = True
55 | self.checked = False
56 | def onClick(self):
57 | layer = pythonaddins.GetSelectedTOCLayerOrDataFrame()
58 | print(layer)
59 | clear(layer)
60 |
--------------------------------------------------------------------------------
/addins/definitionquery/README.txt:
--------------------------------------------------------------------------------
1 | This is a stub project created by the ArcGIS Desktop Python AddIn Wizard.
2 |
3 | MANIFEST
4 | ========
5 |
6 | README.txt : This file
7 |
8 | makeaddin.py : A script that will create a .esriaddin file out of this
9 | project, suitable for sharing or deployment
10 |
11 | config.xml : The AddIn configuration file
12 |
13 | Images/* : all UI images for the project (icons, images for buttons,
14 | etc)
15 |
16 | Install/* : The Python project used for the implementation of the
17 | AddIn. The specific python script to be used as the root
18 | module is specified in config.xml.
19 |
--------------------------------------------------------------------------------
/addins/definitionquery/addins.esriaddin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/addins/definitionquery/addins.esriaddin
--------------------------------------------------------------------------------
/addins/definitionquery/makeaddin.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import zipfile
4 |
5 | current_path = os.path.dirname(os.path.abspath(__file__))
6 |
7 | out_zip_name = os.path.join(current_path,
8 | os.path.basename(current_path) + ".esriaddin")
9 |
10 | BACKUP_FILE_PATTERN = re.compile(".*_addin_[0-9]+[.]py$", re.IGNORECASE)
11 |
12 | def looks_like_a_backup(filename):
13 | return bool(BACKUP_FILE_PATTERN.match(filename))
14 |
15 | with zipfile.ZipFile(out_zip_name, 'w', zipfile.ZIP_DEFLATED) as zip_file:
16 | for filename in ('config.xml', 'README.txt', 'makeaddin.py'):
17 | zip_file.write(os.path.join(current_path, filename), filename)
18 | dirs_to_add = ['Images', 'Install']
19 | for directory in dirs_to_add:
20 | for (path, dirs, files) in os.walk(os.path.join(current_path,
21 | directory)):
22 | archive_path = os.path.relpath(path, current_path)
23 | found_file = False
24 | for file in (f for f in files if not looks_like_a_backup(f)):
25 | archive_file = os.path.join(archive_path, file)
26 | print archive_file
27 | zip_file.write(os.path.join(path, file), archive_file)
28 | found_file = True
29 | if not found_file:
30 | zip_file.writestr(os.path.join(archive_path,
31 | 'placeholder.txt'),
32 | "(Empty directory)")
33 |
--------------------------------------------------------------------------------
/img/Signature.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/img/Signature.PNG
--------------------------------------------------------------------------------
/img/defquery_addin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/img/defquery_addin.gif
--------------------------------------------------------------------------------
/img/line_endpoint.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/img/line_endpoint.PNG
--------------------------------------------------------------------------------
/lib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/lib/__init__.py
--------------------------------------------------------------------------------
/lib/esri/Attachments.py:
--------------------------------------------------------------------------------
1 | from arcpy import AddField_management, ListFields, AddError, Describe, AddMessage
2 | from arcpy.da import UpdateCursor, SearchCursor
3 | from os.path import join
4 | from ..util.File_Operations import verify_path_exists
5 |
6 | def extract_attachments(att_table, out_folder, group_by_field=None):
7 | # [, ...]
8 | l_fields = ListFields(att_table)
9 |
10 | # [dbo.schema.fieldname, ...]
11 | field_names = [f.name for f in l_fields]
12 |
13 | # [DBO.SCHEMA.FIELDNAME, ...]
14 | uppercase = [f.upper() for f in field_names]
15 |
16 |
17 | data_field = None
18 | name_field = None
19 | id_field = None
20 |
21 | data_field = [f for f in uppercase if 'DATA' in f.split('.')][0]
22 | name_field = [f for f in uppercase if 'ATT_NAME' in f.split('.')][0]
23 | id_field = [f.name for f in l_fields if f.type == 'OID'][0]
24 |
25 | fields = [data_field, name_field, id_field]
26 | AddMessage(fields)
27 |
28 | if group_by_field:
29 | if not group_by_field in field_names:
30 | raise Exception('Field {} not found in fields. \n'.format(group_by_field, str(field_names)))
31 | fields.append(group_by_field)
32 |
33 | # verify path
34 | verify_path_exists(out_folder)
35 |
36 | with SearchCursor(att_table, fields) as cursor:
37 | for row in cursor:
38 |
39 | full_out_folder = out_folder
40 | if group_by_field:
41 |
42 | # get the field name
43 | group_folder = row[ fields.index(group_by_field) ]
44 |
45 | full_out_folder = join(out_folder, group_folder)
46 |
47 | # double check folder path
48 | verify_path_exists(full_out_folder)
49 |
50 | # get the attachment file and create a filename
51 | attachment = row[0]
52 | filename = 'ATT_{2}_{1}'.format(*row)
53 |
54 | # write the output file and update the row's value to the file name
55 | open(join(full_out_folder, filename), 'wb').write(attachment.tobytes())
56 |
57 | # cleanup
58 | del row
59 | del filename
60 | del attachment
61 |
62 |
63 |
--------------------------------------------------------------------------------
/lib/esri/Extent.py:
--------------------------------------------------------------------------------
1 | import arcpy
2 |
3 | __author__ = "groemhildt"
4 |
5 | def expand(extent, percent):
6 | '''expands an arcpy.Extent by a given percentage'''
7 | multiplier = percent / float(100)
8 | x_dif = (extent.XMax - extent.XMin) * multiplier
9 | y_dif = (extent.YMax - extent.YMin) * multiplier
10 | return arcpy.Extent(extent.XMin - x_dif, extent.YMin - y_dif,
11 | extent.XMax + x_dif, extent.YMax + y_dif)
12 |
--------------------------------------------------------------------------------
/lib/esri/Geodatabase.py:
--------------------------------------------------------------------------------
1 | #Geodatabase tools
2 |
3 | def get_name(name):
4 | """
5 | retrieves a file gdb friendly name with no '.' dots
6 |
7 | """
8 | return name.split('.')[-1:][0]
9 |
10 |
11 | def delete_existing(path):
12 | from arcpy import Exists, Delete_management, AddMessage
13 | if Exists(path):
14 | AddMessage('File exists and will be removed: {}'.format(path))
15 | Delete_management(path)
16 |
17 | def copy_tables(input_ws, output_ws, foreach_table = None):
18 | """
19 | copies tables or sends each table to a function
20 | input_ws - the input database
21 | output_ws - the output database
22 | foreach_table - the optional function to process each table
23 | """
24 | from arcpy import env, ListTables, AddMessage, AddWarning, \
25 | TableToGeodatabase_conversion, GetCount_management, \
26 | TableToTable_conversion
27 | from os.path import join
28 |
29 | env.workspace = input_ws
30 | for table in ListTables():
31 | AddMessage('Processing table: {}'.format(table))
32 |
33 | if env.skipAttach and '_attach' in table.lower():
34 | AddWarning('Skipping attachments table {}'.format(table))
35 | continue
36 |
37 | if env.skipEmpty:
38 | count = int(GetCount_management(table)[0])
39 | if count == 0:
40 | AddWarning('Skipping because table is empty: {} (empty)'.format(table))
41 | continue
42 |
43 | try:
44 | if foreach_table:
45 | foreach_table(input_ws, output_ws, table)
46 | else:
47 | output_path = join(output_ws, get_name(table))
48 | delete_existing(output_path)
49 | TableToTable_conversion(table, output_ws, get_name(table))
50 | except Exception as e:
51 | AddWarning('Error on table: {} - {}'.format(table, e))
52 | pass
53 |
54 | def process_feature_classes(input_ws, output_ws, foreach_layer = None):
55 | """
56 | processes each featureclass with an optional function
57 | input_ws - the database or dataset path to process feature classes
58 | output_ws - the output for the feature classes
59 | foreach_layer - the function to process the feature classes
60 | """
61 | from arcpy import env, ListFeatureClasses, FeatureClassToGeodatabase_conversion, \
62 | AddWarning, AddMessage, GetCount_management, FeatureClassToFeatureClass_conversion
63 | from os.path import join
64 | env.workspace = input_ws
65 | feature_classes = ListFeatureClasses()
66 | for feature_class in feature_classes:
67 |
68 | AddMessage('Processing {}...'.format(feature_class))
69 | if env.skipEmpty:
70 | count = int(GetCount_management(feature_class)[0])
71 | if count == 0:
72 | AddWarning('Skipping because table is empty: {}'.format(feature_class))
73 | continue
74 | try:
75 | if foreach_layer:
76 | foreach_layer(input_ws, output_ws, feature_class)
77 | else:
78 | #copy each feature class over
79 | output_path = join(output_ws, get_name(feature_class))
80 | delete_existing(output_path)
81 | FeatureClassToFeatureClass_conversion(feature_class, output_ws, get_name(feature_class))
82 | except Exception as e:
83 | AddWarning('Error processing feature class {} - {}'.format(feature_class, e))
84 |
85 |
86 | def process_datasets(from_db, to_db=None,
87 | foreach_layer=None,
88 | foreach_table=None,
89 | foreach_dataset=None):
90 | """
91 | creates the projected datasets necessary and then calls the function
92 | to perform additional functions on each layer and table
93 | from_db - the input database to pull from
94 | to_db - the output database to place the processed data
95 | foreach_layer - the function to process each layer with
96 | foreach_table - the function to process each table with
97 | """
98 | #get the datasets in the input workspace
99 | from arcpy import AddMessage, AddWarning, CreateFeatureDataset_management, ListDatasets, Exists, env, ExecuteError
100 | AddMessage('Workspace: {}'.format(env.workspace))
101 |
102 | #handle feature classes at the top level. these are moved into _top dataset for
103 | #automatic projection handling
104 | AddMessage('Processing tables...')
105 | copy_tables(from_db, to_db, foreach_table)
106 |
107 | AddMessage('Processing feature classes...')
108 | process_feature_classes(from_db, to_db, foreach_layer)
109 |
110 | AddMessage('Processing datasets...')
111 | in_datsets = ListDatasets()
112 | if len(in_datsets):
113 | for dataset in in_datsets:
114 | to_dataset = get_name(dataset)
115 | from_dataset_path = '{}/{}'.format(from_db, dataset)
116 | to_dataset_path = '{}/{}'.format(to_db, to_dataset)
117 | AddMessage('Processing Dataset: {}'.format(from_dataset_path))
118 | try:
119 | if foreach_dataset:
120 | foreach_dataset(from_db, to_db, dataset, skip_empty)
121 | else:
122 | CreateFeatureDataset_management(to_db, to_dataset, env.outputCoordinateSystem)
123 | except ExecuteError as e:
124 | AddWarning('Could not create dataset {}, {}'.format(to_dataset, e))
125 |
126 | process_feature_classes(from_dataset_path, to_dataset_path, foreach_layer)
127 |
128 |
129 | def check_exists(output, name):
130 | from arcpy import Exists, CreateFileGDB_management, AddMessage
131 | from os.path import join
132 | if not Exists(join(output, name)):
133 | AddMessage('GDB does not exist, creating...')
134 | CreateFileGDB_management(output, name)
135 |
--------------------------------------------------------------------------------
/lib/esri/Geometry.py:
--------------------------------------------------------------------------------
1 | from arcpy.da import SearchCursor, InsertCursor
2 | from arcpy import Describe, AddField_management, AddMessage
3 |
4 | def polygon_to_point(input_fc, output_fc):
5 | """
6 | copies the centroid of polygon geometry to
7 | a point feature class
8 | """
9 |
10 | oid_field = Describe(input_fc).OIDFieldName
11 | new_field = 'Rel_OID'
12 |
13 | AddField_management(output_fc, new_field, 'Long')
14 |
15 | search_cursor = SearchCursor(input_fc, ['SHAPE@', oid_field])
16 | insert_cursor = InsertCursor(output_fc, ["SHAPE@", new_field])
17 | spatial_reference = Describe(output_fc).spatialReference
18 |
19 | for row in search_cursor:
20 | point = row[0].projectAs(spatial_reference).centroid
21 | oid = row[1]
22 | insert_cursor.insertRow([point, oid])
23 |
24 | del search_cursor, insert_cursor, spatial_reference
25 |
26 | def line_to_endpoints(input_fc, output_fc, id_field=None):
27 | """
28 | copies the endpoints of a feature class into a point
29 | feature class
30 | """
31 |
32 | # id field can either be provided or obtained automagically
33 | oid_field = id_field if id_field else Describe(input_fc).OIDFieldName
34 |
35 | # we add two custom fields to track the related id and the type of end point
36 | related_field = 'related_oid'
37 | type_field = 'point_type'
38 | AddField_management(output_fc, related_field, 'LONG')
39 | AddField_management(output_fc, type_field, 'TEXT', field_length=10)
40 |
41 |
42 | fields = [f.name for f in Describe(input_fc).fields if f.name not in ['SHAPE@', 'Shape', 'Shape_Length']]
43 | output_fields = fields + ['SHAPE@', related_field, type_field]
44 |
45 | #shape will be the last column selected
46 | search_cursor = SearchCursor(input_fc, fields + ['SHAPE@'])
47 |
48 | #our insert cursor
49 | insert_cursor = InsertCursor(output_fc, output_fields)
50 |
51 | #identify the spatial reference for projecting the geometry
52 | spatial_reference = Describe(output_fc).spatialReference
53 |
54 | for row in search_cursor:
55 | # project and store the geometry
56 | geom = row[len(row) - 1].projectAs(spatial_reference)
57 |
58 | # get the row id
59 | oid = row[fields.index(oid_field)]
60 |
61 | # insert two entries but with our new custom geometry
62 | insert_cursor.insertRow(row[:-1] + (geom.firstPoint, oid, 'START'))
63 | insert_cursor.insertRow(row[:-1] + (geom.lastPoint, oid, 'END'))
64 |
65 | del search_cursor, insert_cursor, spatial_reference
66 |
--------------------------------------------------------------------------------
/lib/esri/MapDocument.py:
--------------------------------------------------------------------------------
1 | import arcpy
2 | import os
3 |
4 | def export_and_append(export_location, final_pdf, document = None ):
5 | """exports a temporary map document, then appends it to an existing pdf document"""
6 | if not document:
7 | document = arcpy.mapping.MapDocument("CURRENT")
8 | temp = os.path.join(export_location, 'temp.pdf')
9 | arcpy.AddMessage('Exporting pdf: {}'.format(document.title))
10 | arcpy.mapping.ExportToPDF(document, temp)
11 | #append it
12 | final_pdf.appendPages(temp)
13 | #remove the temp
14 | arcpy.Delete_management(temp)
15 |
16 | def add_map_layer(file_name, symbol_layer, name='layer', data_frame = None):
17 | """creates and adds a symbolized layer to a data frame"""
18 | if not data_frame:
19 | data_frame = arcpy.mapping.ListDataFrames(arcpy.mapping.MapDocument("CURRENT"))[0]
20 | layer = arcpy.mapping.Layer(file_name)
21 | layer.name = name
22 | arcpy.ApplySymbologyFromLayer_management(layer, symbol_layer)
23 | arcpy.mapping.AddLayer(data_frame, layer)
24 | return layer
25 |
--------------------------------------------------------------------------------
/lib/esri/ProDataDrivenPages.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # A set of data driven pages like utilities for arcgis pro
3 | #
4 | # Usage:
5 | # import sys
6 | # sys.path.append('C:/path/to/Python_Tools')
7 | # from scripts.ProDataDrivenPages import export_layer_maps, FeatureExtentIter, MapLayoutHelper
8 | # help(export_layer_maps)
9 | # ...
10 |
11 | from lib.esri.Extent import expand
12 | from lib.util.String import get_safe_string
13 |
14 | def export_layer_maps(helper, title, layer_key='Map:', title_element='Title', subtitle_element='Subtitle', output='C:/Temp/', file_suffix=''):
15 | """
16 | exports a set of maps by toggling a filtered set of layers
17 | By default, it searches for layers that start with 'Map:' and
18 | uses this list to generate a set of maps
19 | helper - A MapLayoutHelper
20 | title - The primary title of the map
21 | layer_key - (optional) the string to filter map layers on. The default is 'Map:'
22 | title_element - (optional) the string to search for a title text element. This item
23 | will be updated with the title text
24 | subtitle_element - (optional) the string to search for a subtitle text element.
25 | This item will be updated to the name of the layer, minus the layer_key text.
26 | If a layer's name is Map:Aerial, the subtitle will be set to "Aerial"
27 | output - (optional) The output folder path.
28 | file_suffix - (optional) The default filename will be the title_subtitle.pdf,
29 | adding this value will add an additional string to the end of the filepath.
30 | """
31 |
32 | # get a list of layers to use for "maps"
33 | # set them all to not visible for starters
34 | layers = [l for l in helper.map.map.listLayers() if layer_key in l.name]
35 | for l in layers:
36 | l.visible = False
37 |
38 | # get the text elements
39 | subtitle_element = helper.layout.listElements('TEXT_ELEMENT', subtitle_element)[0]
40 | title_element = helper.layout.listElements('TEXT_ELEMENT', title_element)[0]
41 | title_element.text = title
42 |
43 | # Go through each layer, set it to visible, and export a pdf
44 | for l in layers:
45 | l.visible = True
46 | name = l.name.split(':')[1]
47 | subtitle_element.text = name
48 | filename = get_safe_string('{}_{}_{}'.format(title, name, file_suffix))
49 | helper.layout.exportToPDF(output + filename)
50 | l.visible = False
51 |
52 | class FeatureExtentIter:
53 | """
54 | A quick helper to iterate through a feature layer and set a map extent.
55 | This is similar to data driven pages in ArcMap. (Not tested with points!)
56 | Usage:
57 | helper = MapLayoutHelper('CURRENT')
58 | rows = FeatureExtentIter(helper, iter_layer='My Layer', expand_extent=50)
59 | for row in rows:
60 | helper.layout.exportToPDF('C:/Temp/' + row[3])
61 | Constructor:
62 | helper - the MapLayoutHelper
63 | iter_layer - The layer to iterate through
64 | expand_extent - The percent to expand each feature's extent
65 | """
66 | def __init__(self, helper, iter_layer='Industrial Development Sites', expand_extent=25):
67 | from arcpy.da import SearchCursor
68 | from arcpy import Describe
69 | layer = helper.map.map.listLayers(iter_layer)[0]
70 | fields = ['SHAPE@'] + [f.name for f in Describe(layer).fields]
71 | self.helper = helper
72 | self.cursor = SearchCursor(layer, fields)
73 | self.expand_extent = expand_extent
74 |
75 | def __iter__(self):
76 | return self
77 |
78 | def __next__(self):
79 | row = next(self.cursor)
80 | if not row:
81 | raise StopIteration
82 | self.helper.map.camera.setExtent(expand(row[0].extent, self.expand_extent))
83 | return row
84 |
85 | class MapLayoutHelper(object):
86 | """
87 | A helper object to initialize a project, layout, and a map for easy access.
88 | Usage:
89 | MapLayoutHelper('CURRENT')
90 | Constructor:
91 | project - (optional) The path to the ArcGIS Pro project or the text "CURRENT"
92 | if you are using an open proejct. The default is 'CURRENT'.
93 | layout - (optional) The name of the layout to initialize. The default
94 | is 'Template'
95 | map - (optional) The name of the map element inside the layout provided.
96 | The default is 'Map'
97 | """
98 | def __init__(self, project='CURRENT', layout='Template', map='Map'):
99 | from arcpy.mp import ArcGISProject
100 | self.project = ArcGISProject(project)
101 | self.layout = self.project.listLayouts(layout)[0]
102 | self.map = self.layout.listElements('MAPFRAME_ELEMENT', map)[0]
103 |
--------------------------------------------------------------------------------
/lib/esri/Raster.py:
--------------------------------------------------------------------------------
1 | #
2 | # Thanks to FelixIP
3 | # http://gis.stackexchange.com/questions/187322/extracting-values-to-points-without-arcgis-spatial-analyst
4 |
5 | from arcpy.da import UpdateCursor, SearchCursor
6 | from arcpy import Describe, RasterToNumPyArray, PointGeometry, Point, SetProgressor, SetProgressorPosition, ResetProgressor, MakeTableView_management, GetCount_management, AddMessage, CalculateField_management
7 |
8 | def getElevationAtPoint(raster, point):
9 | """
10 | retrieves the elevation at a point
11 | """
12 |
13 | # project the point
14 | sr = Describe(raster).spatialReference
15 | projected_point = point.projectAs(sr).getPart(0)
16 |
17 | # Get the first cell that starts at the point
18 | result = RasterToNumPyArray(raster, projected_point, 1, 1, -999)
19 | return result[0][0]
20 |
21 | def calculatePointElevationField(points, raster, field_name):
22 |
23 | #monitor progress by counting features
24 | view = MakeTableView_management(points, 'points')
25 | count = int(GetCount_management('points').getOutput(0))
26 | SetProgressor('step', 'Extracting point elevations', 0, count)
27 | AddMessage('{} features to process'.format(count))
28 |
29 | # Get the object id field
30 | oid = Describe(points).OIDFieldName
31 |
32 | # make an update cursor and update each row's elevation field
33 | cursor = UpdateCursor(points, [field_name, 'SHAPE@', oid])
34 |
35 | # make a temporary dict to store our elevation values we extract
36 | elevations = {}
37 |
38 | for row in cursor:
39 | row[0] = getElevationAtPoint(raster, row[1])
40 | cursor.updateRow(row)
41 | AddMessage('row updated to {}; oid: {}'.format(row[0], row[2]))
42 | SetProgressorPosition()
43 |
44 | # release the data
45 | del cursor
46 |
47 | #reset this progressor
48 | ResetProgressor()
49 |
--------------------------------------------------------------------------------
/lib/esri/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/lib/esri/__init__.py
--------------------------------------------------------------------------------
/lib/util/File_Operations.py:
--------------------------------------------------------------------------------
1 | #directory operation functions
2 | from os.path import split, join, isfile, exists
3 | from os import makedirs
4 | import errno
5 | from shutil import copyfile
6 | from arcpy.da import SearchCursor
7 |
8 | def verify_path_exists(path):
9 | """
10 | verifies that the path exists and if not
11 | creates it
12 | """
13 | try:
14 | makedirs(path)
15 | except OSError as exception:
16 | if exception.errno != errno.EEXIST:
17 | raise
18 |
19 | try:
20 | from PyPDF2 import PdfFileWriter, PdfFileReader
21 | except ImportError as e:
22 | print('WARNING: PyPDF2 is not available, and is used to extract pdf pages: {}'.format(e))
23 |
24 | def clean_folder(folder):
25 | """
26 | tries to remove all files in a folder recursively
27 | """
28 | import os, shutil
29 |
30 | for the_file in os.listdir(folder):
31 |
32 | # skip log files
33 | if '.log' in the_file:
34 | continue
35 | file_path = os.path.join(folder, the_file)
36 | try:
37 | if os.path.isfile(file_path):
38 | os.unlink(file_path)
39 | elif os.path.isdir(file_path): shutil.rmtree(file_path)
40 | except Exception as e:
41 | print(e)
42 |
43 | def get_page_filename(filename, page):
44 | """
45 | formats a filename
46 | """
47 | return "Page_{}.pdf".format(page + 1)
48 |
49 | def copy_file(input_folder, destination_folder, filename):
50 | """
51 | copy a file from a path into a new directory, creating new directories where necessary
52 | duplicating the directory structure
53 | input_folder - The input file folder
54 | destination_folder - The destination file folder
55 | filename - the name of the file to copy
56 | """
57 |
58 | filepath = join(input_folder, filename)
59 | #make sure the file exists
60 | if not isfile(filepath):
61 | print('{} is not a file'.format(filepath))
62 | return False
63 |
64 | # make sure the output folder exists
65 | verify_path_exists(destination_folder)
66 |
67 | #copy the file
68 | new_filepath = join(destination_folder, filename)
69 | if not isfile(new_filepath):
70 | copyfile(filepath, new_filepath)
71 | print('Created file: {}'.format(new_filepath))
72 | return True
73 |
74 | def extract_page(input_file, number, output_path, filename_formatter=get_page_filename):
75 | """
76 | extracts a page from a pdf and saves it to a file
77 | input_file - the input file pdf path
78 | number - the page number to extract
79 | format_string - the string formatter that recieves the original filename and page number
80 | """
81 |
82 | if not isfile(input_file):
83 | print ('{} is not a file'.format(input_file))
84 | return False
85 |
86 | #get a file name
87 | file_parts = split(input_file)
88 | file_name = file_parts[1].split('.pdf')[0]
89 |
90 | #check if file exists and skip if it does
91 | if not output_path:
92 | output_path = file_parts[0]
93 | output_file = join(output_path, filename_formatter(file_name, number).strip('/'))
94 | verify_path_exists(split(output_file)[0])
95 | if exists(output_file):
96 | return True
97 |
98 | #initialize the pdf reader/writer
99 | input_pdf = PdfFileReader(open(input_file, 'rb'))
100 | output_pdf = PdfFileWriter()
101 |
102 | #extract the page
103 | page = None
104 | try:
105 | page = input_pdf.getPage(number)
106 | except:
107 | print('error extracting page from {}'.format(input_file))
108 | return False
109 |
110 | #add the page to the output writer
111 | output_pdf.addPage(page)
112 |
113 | print('writing output - {}'.format(output_file))
114 | with open(output_file, 'wb') as output:
115 | try:
116 | output_pdf.write(output)
117 | except:
118 | print('error while writing output to {}'.format(output_file))
119 | return False
120 | return True
121 |
122 | def copy_layer_filepath(layer, output_base, fields, log_file=None):
123 | """
124 | copies files in a path to a new path and splits the required pages into
125 | separate files for quicker loading
126 | layer: the path to the layer or layer name in workspace
127 | output_base: the base path to the output file
128 | fields: A list of field names to use. Order should be like this:
129 | [path, name, doc_id, page_num]
130 | log_file: an open file or class with a `write` method
131 | """
132 | cursor = SearchCursor(layer, fields)
133 | print('Scanning layer - {}'.format(layer))
134 | if log_file:
135 | print ('Writing logfile - {}'.format(log_file))
136 | log_file.write('Problem,Layer,Plan,Page\n')
137 |
138 | # a dict to keep track of which files/pages to copy
139 | # each key references a file, and the value is a list of pages to copy
140 | file_list = {}
141 | for row in cursor:
142 | #unpack row
143 | folder, name, doc_id, page_num = row
144 |
145 | plan = join(folder, name)
146 |
147 | #make sure the plan isn't already in the dict
148 | if not doc_id in file_list:
149 | file_list[doc_id] = {
150 | 'pages': [],
151 | 'folder': folder,
152 | 'filename': name
153 | }
154 |
155 | #add the page index to the list
156 | #page numbers are one more than the page index so we
157 | #subtract 1 because the index is one less than the page number
158 | try:
159 | page_num = int(page_num) - 1
160 | if page_num < 0:
161 | page_num = 0
162 | except:
163 | #handle invalid values (null)
164 | page_num = 0
165 | if not page_num in file_list[doc_id]['pages']:
166 | file_list[doc_id]['pages'].append(page_num)
167 |
168 | for plan, props in file_list.iteritems():
169 | input_folder = props['folder']
170 | pages = props['pages']
171 | filename = props['filename']
172 | input_file = join(input_folder, filename)
173 | output_folder = join(output_base, filename)
174 |
175 | #copy the entire file
176 | #log errors
177 | if not copy_file(input_folder, output_folder, filename) and log_file:
178 | log_file.write('Copy file failed,{},{},{} \n'.format(layer, plan, -99))
179 | if filename.split('.')[-1].upper() == 'PDF':
180 | for page in pages:
181 | # and split the pages
182 | #log errors for future purposes
183 | #functions return false if error occurs
184 | if not extract_page(input_file, page, output_folder) and log_file:
185 | log_file.write('Extract page failed,{},{},{} \n'.format(layer, plan, page))
186 | print('----Done-----!\n\n')
187 |
--------------------------------------------------------------------------------
/lib/util/String.py:
--------------------------------------------------------------------------------
1 | #a collection of string functions
2 |
3 | def get_safe_string(text):
4 | """returns a safe string that can be used for file names"""
5 | return "".join([c for c in text if c.isalnum()]).rstrip()
6 |
--------------------------------------------------------------------------------
/lib/util/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = "groemhildt"
2 | __date__ = "$Aug 5, 2015 2:04:32 PM$"
--------------------------------------------------------------------------------
/scripts/LabelRelatedRecords.py:
--------------------------------------------------------------------------------
1 | # Calculates the dynamic expression or label for a layer and can be used
2 | # to dynamically display related records text for data driven pages
3 | # or feature labels
4 |
5 | # Change the FindLabel parameter to the id field of the main table
6 | # This id should be related to each of the related tables
7 | # Also change the first line of FindLabel. Set parameter equal to the FindLabel parameter
8 | def FindLabel ( [PARCELID] ):
9 | return get_lookup_text([PARCELID])
10 |
11 | # create formatter functions to return what you want displayed
12 | # for each row. These should return plain text for each row.
13 |
14 | # returns a string with the parcel zone
15 | def get_parcel(row):
16 | return row["ZONE"]
17 |
18 | # change the lookup tables and assign their appropriate formatter function
19 | # Properties of each lookup item:
20 | # table: the name of the table in the mxd
21 | # row_formatter: the function that will format each row's properties and return
22 | # the text string
23 | # id_field: the name of the field in the table that is related to the
24 | # id passed in to FindLabel
25 | # fields: list of fields by name (optiononal) default '*'
26 | lookup_tables = [{
27 | "table": "Related_Zones",
28 | "row_formatter": get_parcel,
29 | "id_field": "PARCELID",
30 | "fields": ['PARCELID', 'ZONE'],
31 | "header": 'Parcel Zones:',
32 | },
33 | #other lookup tables...
34 | ]
35 |
36 |
37 | def get_lookup_text(id):
38 | # init the return value
39 | text = ''
40 | if id is not None:
41 | for table in lookup_tables:
42 | if table.has_key('header'):
43 | text += "{} \n".format(table['header'])
44 | if type(id) is str or type(id) is unicode:
45 | query = "{} = '{}'".format(table["id_field"], id)
46 | else:
47 | query = "{} = {}".format(table["id_field"], id)
48 | fields = table['fields'] if table.has_key('fields') else '*'
49 | with arcpy.da.SearchCursor(table["table"], fields, where_clause=query) as rows:
50 | for row in rows:
51 | dictionary = dict()
52 | for idx, val in enumerate(rows.fields):
53 | dictionary[val] = row[idx]
54 | text += "{} \n".format(table["row_formatter"](dictionary))
55 | return text
56 |
--------------------------------------------------------------------------------
/scripts/Reproject.py:
--------------------------------------------------------------------------------
1 | import lib.Geodatabase
2 | import lib.Reproject_Tool import Reproject
3 |
4 | def run_tool(from_db, to_db, projection):
5 | Geodatabase.clean(to_db)
6 | r = Reproject()
7 | r.reproject(from_db, to_db, projection)
8 |
9 | #defaults if run at command line
10 | if __name__ == '__main__':
11 | #defaul values
12 | from_db = 'N:/BaseData/data.gdb'
13 | to_db = 'C:/Data/projected.gdb'
14 | projection = 'N:/BaseData/proj.prj'
15 |
16 | #run the tool
17 | run_tool(from_db, to_db, projection)
18 |
--------------------------------------------------------------------------------
/scripts/UpdateMXDMetadata.py:
--------------------------------------------------------------------------------
1 | from xml.dom.minidom import parse
2 | from arcpy import GetInstallInfo, AddError
3 | import sys
4 | try:
5 | from arcpy.mapping import MapDocument, ListDataFrames, ListLayers
6 | from arcpy import ExportMetadata_conversion
7 | except ImportError as e:
8 | AddError('This tool requires ArcMap: {}'.format(e))
9 | print('This tool requires ArcMap: {}'.format(e))
10 |
11 | def update_metadata(document='CURRENT'):
12 | """
13 | updates metadata in an arcmap document
14 | document - (optional) the path to an arcmap document or the keyword 'CURRENT'.
15 | The default is 'CURRENT'
16 | """
17 | #set local variables
18 | dir = GetInstallInfo("desktop")["InstallDir"]
19 | translator = dir + "Metadata/Translator/ESRI_ISO2ISO19139.xml"
20 | mxd = MapDocument(document)
21 | df = ListDataFrames(mxd)
22 | temp_path = "C:/temp"
23 | for layer in ListLayers(mxd, "*", df[0]):
24 | if not layer.isGroupLayer:
25 | description_text = ""
26 | path = temp_path + '/' + layer.datasetName + '.xml'
27 | print(path)
28 | ExportMetadata_conversion(layer.dataSource, translator, path)
29 | dom = parse(path)
30 | fields = ('abstract', 'purpose', 'credit')
31 | for field in fields:
32 | tags = dom.getElementsByTagName(field)
33 | print(str( len(tags) ) + ' | ' + str( tags ))
34 | if len(tags):
35 | tag_string = tags[0].getElementsByTagName('gco:CharacterString')[0].childNodes[0].nodeValue
36 | description_text = "{}
{}: {}
".format(description_text, field.capitalize(), tag_string)
37 | if field == 'credit':
38 | layer.credit = tag_string
39 | print(description_text)
40 | layer.description = description_text
41 |
42 | if __name__ == '__main__':
43 | update_metadata(sys.argv[1])
44 |
--------------------------------------------------------------------------------
/scripts/layer_selection.py:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | # A better way to create selection set layers
4 | # This method uses object id's to create a definition expression
5 | # rather than a weird layer that we can't modify later like
6 | # arcmap's builtin method
7 | #
8 | #
9 |
10 | import arcpy
11 |
12 | # always use current
13 | doc = arcpy.mapping.MapDocument('current')
14 |
15 | def get_layer(layer):
16 | try:
17 | layer = [l for l in arcpy.mapping.ListLayers(doc) if l.name == layer][0]
18 | return layer
19 | except IndexError as e:
20 | raise Exception('Layer not found::{layer}'.format(layer=layer))
21 | return
22 |
23 | def create(layer):
24 | layer = get_layer(layer)
25 |
26 | # check selection set
27 | ids = [str(id) for id in layer.getSelectionSet()]
28 | if not len(ids):
29 | print('No selections on current layer')
30 | return
31 |
32 | id_field = arcpy.Describe(layer).OIDFieldName
33 |
34 | # set definition query
35 | layer.definitionQuery = '{field} IN ({ids})'.format(field=id_field, ids=','.join(ids))
36 |
37 | def clear(layer):
38 | layer = get_layer(layer)
39 | layer.definitionQuery = ''
40 |
--------------------------------------------------------------------------------
/scripts/update_related_ids.py:
--------------------------------------------------------------------------------
1 | from ..esri.Geometry import line_to_endpoints
2 | import arcpy
3 |
4 | arcpy.AddToolbox('../Utilities.pyt')
5 |
6 | related_layers = [{
7 | 'layer': 'storm_pipe',
8 | 'related': 'storm_struc',
9 | 'from_foreign_key': 'from_struc_id',
10 | 'to_foreign_key': 'to_struc_id',
11 | 'reference_key': 'cid'
12 | }]
13 |
14 | for layer in related_layers:
15 | print('processing layer {}...'.format(layer['layer']))
16 |
17 | # create the points layer
18 | foreign_key = 'related_oid'
19 | points = 'in_memory/end_point'
20 | points = arcpy.Utilities.LineEndPoints(layer['layer'], 'false', points).getOutput(0)
21 | print('points layer created: {}'.format(points))
22 |
23 | # spatially join to the related point layer
24 | joined_points = 'in_memory/joined_points'
25 | joined_points = arcpy.SpatialJoin_analysis(points, layer['related'], joined_points).getOutput(0)
26 | print('spatial join completed: {}'.format(joined_points))
27 |
28 | # add a join on the line layer
29 | line_layer = arcpy.MakeFeatureLayer_management(layer['layer'], 'line_layer').getOutput(0)
30 | oid_field = arcpy.Describe(line_layer).OIDFieldName
31 | arcpy.AddJoin_management(joined_points, foreign_key, line_layer, oid_field)
32 | print('join added: {}'.format(joined_points))
33 |
34 | # create two feature layers one for each related field
35 | start_points = arcpy.MakeFeatureLayer_management(joined_points, 'start_points', where_clause='point_type="START"').getOutput(0)
36 | end_points = arcpy.MakeFeatureLayer_management(joined_points, 'end_points', where_clause='point_type="END"').getOutput(0)
37 | print('layers created: {}, {}'.format(start_points, end_points))
38 |
39 | # calculate the fields new values
40 | arcpy.CalculateField_management(start_points, layer['from_foreign_key'], layer['reference_key'])
41 | arcpy.CalculateField_management(end_points, layer['to_foreign_key'], layer['reference_key'])
42 |
--------------------------------------------------------------------------------
/tools/ClipTool.py:
--------------------------------------------------------------------------------
1 | from lib.esri import Geodatabase
2 | from arcpy import Parameter, AddMessage, Exists, Clip_analysis, Delete_management, FeatureClassToFeatureClass_conversion, env
3 |
4 | #arcpy toolbox
5 | #parameter indexes
6 | clip_from_db = 0
7 | clip_to_db = 1
8 | clip_projection = 2
9 | clip_clip_layer = 3
10 |
11 | class Clip(object):
12 | """arcpy toolbox for clipping an entire database"""
13 | def __init__(self):
14 | self.label = 'Clip Geodatabase'
15 | self.description = """
16 | Clip an entire database preserving datasets and
17 | feature class names
18 | """
19 | def getParameterInfo(self):
20 | return [Parameter(
21 | displayName = 'From Database',
22 | name = 'from_db',
23 | direction = 'Input',
24 | datatype = 'Workspace',
25 | parameterType = 'Required',
26 | ), Parameter(
27 | displayName = 'To Database (Existing features in here will be deleted!)',
28 | name = 'to_db',
29 | direction = 'Input',
30 | datatype = 'Workspace',
31 | parameterType = 'Required',
32 | ), Parameter(
33 | displayName = 'Projection File',
34 | name = 'projection',
35 | direction = 'Input',
36 | datatype = 'DEPrjFile',
37 | parameterType = 'Required',
38 | ), Parameter(
39 | displayName = 'Clipping Layer',
40 | name = 'clip_layer',
41 | direction = 'Input',
42 | datatype = 'DEFeatureClass',
43 | parameterType = 'Required',
44 | )]
45 | def execute(self, parameters, messages):
46 | AddMessage('{}; {}; {};'.format(
47 | parameters[clip_from_db].valueAsText,
48 | parameters[clip_to_db].valueAsText,
49 | parameters[clip_projection].valueAsText))
50 | from_db = parameters[clip_from_db].valueAsText
51 | to_db = parameters[clip_to_db].valueAsText
52 | projection = parameters[clip_projection].valueAsText
53 | clip_layer = parameters[clip_clip_layer].valueAsText
54 |
55 | self.clip(from_db, to_db, projection, clip_layer)
56 |
57 | def clip(self, from_db, to_db, projection, clip_layer):
58 |
59 | if not Exists(projection):
60 | AddMessage('Projection file {} does not exist'.format(projection))
61 | return
62 | def foreach_layer(from_dataset_path, to_dataset_path, feature_class):
63 | from_feature_path = '{}/{}'.format(from_dataset_path, feature_class)
64 | to_feature_path = '{}/{}'.format(to_dataset_path, feature_class.split('.')[-1:][0])
65 |
66 | Clip_analysis('{}/{}'.format(from_dataset_path, feature_class), clip_layer, to_feature_path)
67 |
68 | Geodatabase.process_datasets(from_db,
69 | to_db,
70 | projection,
71 | foreach_layer=foreach_layer)
72 |
--------------------------------------------------------------------------------
/tools/DataUpdater.py:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------------------------------------
2 | # Name: Data_Updater
3 | # Purpose: See tool metadata
4 | #
5 | # Author: groemhildt
6 | #
7 | # Created: 17/03/2016
8 | # Copyright: (c) groemhildt 2016
9 | #-------------------------------------------------------------------------------
10 | from arcpy import Parameter, AddError
11 | from arcpy.da import UpdateCursor
12 | try:
13 | from arcpy.mp import ArcGISProject
14 | except ImportError as e:
15 | AddError('This script requires ArcGIS Pro: {}'.format(e))
16 |
17 |
18 | def update_layer(layer, field, value, new_value):
19 | if(field and value):
20 | where_clause = '{} = \'{}\''.format(field, value)
21 | else:
22 | where_clause = None
23 | try:
24 | cursor = UpdateCursor(layer, field, where_clause)
25 | except(TypeError):
26 | return "Error loading table {}".format(layer)
27 |
28 | try:
29 | for row in cursor:
30 | row[0] = new_value
31 | cursor.updateRow(row);
32 | except(RuntimeError):
33 | del cursor
34 | return "Error modifying table {}".format(layer)
35 |
36 | return 'Layer Updated: {}'.format(layer)
37 |
38 | def get_layer_names(map='Map'):
39 | doc = ArcGISProject('current')
40 | m = doc.listMaps(map)[0]
41 |
42 | return [layer.name for layer in map.listLayers() if layer.visible and layer.isFeatureLayer]
43 |
44 | def main():
45 | layers = get_layer_names()
46 | for layer in layers:
47 | print(update_layer(layer, 'Plan_ID', '1415', '1415TownSquareLn'))
48 |
49 | if __name__ == '__main__':
50 | main()
51 |
52 | class MultipleLayerUpdater(object):
53 | def __init__(self):
54 | self.label = 'Multiple Layer Updater'
55 | self.description = """
56 | Updates a field in multiple layers in the current map document with a value.
57 | If the field is not visible or is not a feature layer it will be skipped.
58 | """
59 | self.canRunInBackground = False
60 |
61 | def getParameterInfo(self):
62 | return [Parameter(
63 | displayName='Map',
64 | name='map',
65 | datatype='GPString',
66 | parameterType='Required',
67 | direction='Input'
68 | ),Parameter(
69 | displayName='Field',
70 | name='field',
71 | datatype='GPString',
72 | parameterType='Required',
73 | direction='Input'
74 | ), Parameter(
75 | displayName='Old Value',
76 | name='old_value',
77 | datatype='GPString',
78 | parameterType='Required',
79 | direction='Input'
80 | ), Parameter(
81 | displayName='New Value',
82 | name='new_value',
83 | datatype='GPString',
84 | parameterType='Required',
85 | direction='Input'
86 | )]
87 |
88 | def execute(self, parameters, messages):
89 | layers= get_layer_names(parameters[0].valueAsText)
90 | for layer in layers:
91 | messages.addMessage(
92 | update_layer(layer, parameters[1].valueAsText, parameters[2].valueAsText,parameters[3].valueAsText))
93 |
--------------------------------------------------------------------------------
/tools/ExtractAttachmentsTool.py:
--------------------------------------------------------------------------------
1 | from arcpy import Parameter, AddField_management, AddMessage, ListFields
2 | from lib.esri.Attachments import extract_attachments
3 |
4 | #parameter indexes
5 | _attach_table = 0
6 | _out_folder = 1
7 | _group_by_field = 2
8 |
9 | class ExtractAttachments(object):
10 | def __init__(self):
11 | self.label = 'Extract table attachments'
12 | self.description = 'Extract table attachments and add a file name field'
13 | self.canRunInBackground = True
14 |
15 | def initializeParameters(self):
16 | self.parameters[_group_by_field].parameterDependencies = [_attach_table]
17 |
18 | def updateParameters(self, parameters):
19 | if parameters[_attach_table]:
20 | layer = parameters[_attach_table].valueAsText
21 | parameters[_group_by_field].filter.list = [f.name for f in ListFields(layer)]
22 |
23 | def getParameterInfo(self):
24 | """
25 | defines the parameters
26 | """
27 | params = [Parameter(
28 | displayName='Input attachment table',
29 | name='attach_table',
30 | datatype=['Table', 'GPTableView'],
31 | parameterType='Required',
32 | direction='Input'
33 | ), Parameter(
34 | displayName='Output attachments folder',
35 | name='out_folder',
36 | datatype=['DEFolder'],
37 | parameterType='Required',
38 | direction='Input'
39 | ), Parameter(
40 | displayName='Group By Field (place images in subfolders)',
41 | name='group_by_field',
42 | datatype='GPString',
43 | parameterType='Optional',
44 | direction='Input'
45 | )]
46 | return params
47 |
48 | def execute(self, params, messages):
49 | """
50 | gets the parameters and passes them to process
51 | """
52 |
53 | attach_table = params[_attach_table].valueAsText
54 | out_folder = params[_out_folder].valueAsText
55 | group_by_field = params[_group_by_field].valueAsText
56 |
57 | # run the task
58 | extract_attachments(attach_table, out_folder, group_by_field)
59 |
60 |
--------------------------------------------------------------------------------
/tools/FTPMirrorTool.py:
--------------------------------------------------------------------------------
1 | #
2 | # Download an ftp directory to a local folder as a scriptable
3 | # and model builder friendly arcpy tool
4 | # http://stackoverflow.com/questions/5230966/python-ftp-download-all-files-in-directory
5 | #
6 |
7 | from arcpy import Parameter, AddMessage, AddWarning
8 | from os.path import join
9 | from lib.util.File_Operations import verify_path_exists
10 | from ftplib import FTP
11 |
12 | #paramter indexes
13 | _output_folder = 0
14 | _ftp_folder = 1
15 | _ftp_server = 2
16 | _ftp_username = 3
17 | _ftp_password = 4
18 |
19 | def is_file(ftp, filename):
20 | try:
21 | ftp.cwd(filename)
22 | except Exception as e:
23 | return True
24 | return False
25 |
26 | def retrieve_directory_recursive(ftp, output_folder, directory, pattern=None):
27 | """
28 | recursively retrieves files from an ftp directory
29 | """
30 |
31 | verify_path_exists(output_folder)
32 | AddMessage('Navigating to {}'.format(directory))
33 |
34 | if is_file(ftp, directory):
35 | AddMessage('Processing file: {}'.format(directory))
36 | local_filename = join(output_folder, directory)
37 |
38 | if pattern is not None and pattern not in directory:
39 | return
40 | try:
41 | f = open(local_filename, 'wb')
42 | ftp.retrbinary('RETR '+ directory, f.write)
43 | f.close()
44 | except Exception as e:
45 | AddWarning('Could not access file {}'.format(directory))
46 | else:
47 | output_folder = join(output_folder, directory)
48 | filenames = ftp.nlst() # get filenames within the directory
49 |
50 | #navigate sub directories
51 | for d in filenames:
52 | retrieve_directory_recursive(ftp, output_folder, d)
53 |
54 |
55 | class FTPMirror(object):
56 | def __init__(self):
57 | """
58 | Define the tool (tool name is the name of the class).
59 | """
60 | self.label = "Mirror FTP Directory"
61 | self.description = 'Downloads an ftp directory to a local drive'
62 | self.canRunInBackground = True
63 |
64 | def getParameterInfo(self):
65 | """
66 | Define parameter definitions
67 | """
68 | params = [Parameter(
69 | displayName='Output Folder',
70 | name='output_folder',
71 | datatype='DEFolder',
72 | parameterType='Required',
73 | direction='Output'
74 | ), Parameter(
75 | displayName='FTP Folder',
76 | name='ftp_folder',
77 | parameterType='Optional',
78 | direction='Input'
79 | ), Parameter(
80 | displayName='FTP Server',
81 | name='ftp_server',
82 | parameterType='Required',
83 | direction='Input'
84 | ), Parameter(
85 | displayName='FTP Username',
86 | name='ftp_username',
87 | parameterType='Optional',
88 | direction='Input'
89 | ), Parameter(
90 | displayName='FTP Password',
91 | name='ftp_password',
92 | parameterType='Optional',
93 | direction='Input'
94 | )]
95 | return params
96 |
97 | def execute(self, params, messages):
98 | """
99 | The source code of the tool
100 | """
101 | output_folder = params[_output_folder].valueAsText
102 | ftp_folder = params[_ftp_folder].valueAsText
103 | ftp_server = params[_ftp_server].valueAsText
104 | ftp_username = params[_ftp_username].valueAsText
105 | ftp_password = params[_ftp_password].valueAsText
106 | self.mirror(output_folder, ftp_folder, ftp_server, ftp_username, ftp_password)
107 |
108 | def mirror(self, output_folder, ftp_folder, ftp_server, ftp_username=None, ftp_password=None, pattern=None):
109 | """
110 | connects to an ftp server and downloads a directory
111 | output_folder - The local path to the download directory
112 | ftp_folder - The path on the server to download or mirror
113 | ftp_server - The server url to the ftp server
114 | ftp_username - The username to use to connect to the ftp server. The default is None
115 | ftp_password - The usernames password to connect to the ftp server. The default is None
116 | AddMessage - A logging function. The default is print
117 | """
118 |
119 | ftp = FTP(ftp_server)
120 |
121 | AddMessage('Logging in.')
122 | ftp.login(ftp_username, ftp_password)
123 | retrieve_directory_recursive(ftp, output_folder, ftp_folder, pattern=pattern)
124 |
125 | ftp.quit() # This is the polite way to close a connection
126 |
--------------------------------------------------------------------------------
/tools/LineEndpointTool.py:
--------------------------------------------------------------------------------
1 | from arcpy import Parameter, CreateFeatureclass_management, Describe, AddMessage
2 | from os.path import split as Split_Path
3 | from lib.esri.Geometry import line_to_endpoints
4 |
5 | #paramter indexes
6 | _input_fc = 0
7 | _use_template = 1
8 | _output_fc = 2
9 |
10 | class LineEndPoints(object):
11 | def __init__(self):
12 | """
13 | Define the tool (tool name is the name of the class).
14 | """
15 | self.label = "Extract Polyline Endpoints"
16 | self.description = 'Converts a polyline geometry into start and endpoints'
17 | self.canRunInBackground = True
18 |
19 | def getParameterInfo(self):
20 | """
21 | Define parameter definitions
22 | """
23 | params = [Parameter(
24 | displayName='Input Polyline Layer',
25 | name='input_fc',
26 | datatype='DEFeatureClass',
27 | parameterType='Required',
28 | direction='Input'
29 | ),
30 | Parameter(
31 | displayName='Use input as template',
32 | name='use_template',
33 | datatype='GPBoolean',
34 | direction='Input'
35 | ),
36 | Parameter(
37 | displayName='Output Points Layer',
38 | name='output_fc',
39 | datatype='DEFeatureClass',
40 | parameterType='Required',
41 | direction='Output'
42 | )]
43 |
44 | params[_use_template].value = False
45 | return params
46 |
47 | def execute(self, params, messages):
48 | """
49 | The source code of the tool
50 | """
51 | input_fc = params[_input_fc].valueAsText
52 | output_fc = params[_output_fc].valueAsText
53 | use_template = params[_use_template].valueAsText
54 | self.process(input_fc, output_fc, use_template)
55 |
56 | def process(self, input_fc, output_fc, use_template='false'):
57 |
58 | template = None
59 | if use_template == 'true':
60 | template = input_fc
61 |
62 | #get the directory, filename, and spatial reference
63 | sp_ref = Describe(input_fc).spatialReference
64 | directory, filename = Split_Path(output_fc)
65 |
66 | #create a new feature class
67 | AddMessage('Creating feature class {}'.format(output_fc))
68 | CreateFeatureclass_management(directory, filename, 'POINT', template, 'DISABLED', 'DISABLED', sp_ref)
69 |
70 | #copy the geometry centroid
71 | AddMessage('Extracting endpoints...')
72 | line_to_endpoints(input_fc, output_fc)
73 |
--------------------------------------------------------------------------------
/tools/PointElevationTool.py:
--------------------------------------------------------------------------------
1 | from arcpy import Parameter
2 | from arcpy import AddField_management
3 | from lib.esri.Raster import calculatePointElevationField
4 |
5 | #parameter indexes
6 | _input_raster = 0
7 | _input_points = 1
8 | _field_name = 2
9 |
10 | class PointElevations(object):
11 | def __init__(self):
12 | self.label = 'Calculate Point Elevations'
13 | self.description = 'Adds an elevation field value from a raster dem'
14 | self.canRunInBackground = True
15 |
16 | def getParameterInfo(self):
17 | """
18 | defines the parameters
19 | """
20 | params = [Parameter(
21 | displayName='Input Raster DEM',
22 | name='input_raster',
23 | datatype=['DERasterDataset', 'DERasterCatalog'],
24 | parameterType='Required',
25 | direction='Input'
26 | ), Parameter(
27 | displayName='Input Points Layer',
28 | name='input_points',
29 | datatype=['DEFeatureClass', 'GPFeatureLayer'],
30 | parameterType='Required',
31 | direction='Input'
32 | ), Parameter(
33 | displayName='New Elevation Field Name',
34 | name='title',
35 | direction='Input',
36 | datatype='GPString',
37 | parameterType='Required',
38 | )]
39 | params[_field_name].value = 'point_elevation'
40 | return params
41 |
42 | def execute(self, params, messages):
43 | """
44 | gets the parameters and passes them to process
45 | """
46 |
47 | input_raster = params[_input_raster].valueAsText
48 | input_points = params[_input_points].valueAsText
49 | field_name = params[_field_name].valueAsText
50 | self.process(input_raster, input_points, field_name)
51 |
52 | def process(self, input_raster, input_points, field_name):
53 | """
54 | creates a new field called point_elevation on the feature class
55 | and populates it with values from the raster at the intersection
56 | """
57 | AddField_management(input_points, field_name, "FLOAT")
58 | calculatePointElevationField(input_points, input_raster, field_name)
59 |
--------------------------------------------------------------------------------
/tools/PolygonCentroidTool.py:
--------------------------------------------------------------------------------
1 | from arcpy import Parameter
2 | from arcpy import CreateFeatureclass_management, Describe
3 | from os.path import split as Split_Path
4 | from lib.esri.Geometry import polygon_to_point
5 |
6 | #paramter indexes
7 | _input_fc = 0
8 | _use_template = 1
9 | _output_fc = 2
10 |
11 | class PolygonCentroidToPoint(object):
12 | def __init__(self):
13 | """
14 | Define the tool (tool name is the name of the class).
15 | """
16 | self.label = "Polygon Centroid To Point"
17 | self.description = 'Converts a polygon into point feature class using the polygon centroid'
18 | self.canRunInBackground = True
19 |
20 | def getParameterInfo(self):
21 | """
22 | Define parameter definitions
23 | """
24 | params = [Parameter(
25 | displayName='Input Polygon Layer',
26 | name='input_fc',
27 | datatype='DEFeatureClass',
28 | parameterType='Required',
29 | direction='Input'
30 | ),
31 | Parameter(
32 | displayName='Use input as template',
33 | name='use_template',
34 | datatype='GPBoolean',
35 | direction='Input'
36 | ),
37 | Parameter(
38 | displayName='Output Points Layer',
39 | name='output_fc',
40 | datatype='DEFeatureClass',
41 | parameterType='Required',
42 | direction='Output'
43 | )]
44 |
45 | params[_use_template].value = False
46 | return params
47 |
48 | def execute(self, params, messages):
49 | """
50 | The source code of the tool
51 | """
52 | input_fc = params[_input_fc].valueAsText
53 | output_fc = params[_output_fc].valueAsText
54 | use_template = params[_use_template].valueAsText
55 |
56 | template = None
57 | if use_template == 'true':
58 | template = input_fc
59 |
60 | #get the directory, filename, and spatial reference
61 | sp_ref = Describe(input_fc).spatialReference
62 | directory, filename = Split_Path(output_fc)
63 |
64 | #create a new feature class
65 | messages.addMessage('Creating feature class {}'.format(output_fc))
66 | CreateFeatureclass_management(directory, filename, 'POINT', template, 'DISABLED', 'DISABLED', sp_ref)
67 |
68 | #copy the geometry centroid
69 | messages.addMessage('Copying polygon centroids...')
70 | polygon_to_point(input_fc, output_fc)
71 |
--------------------------------------------------------------------------------
/tools/ReprojectTool.py:
--------------------------------------------------------------------------------
1 | from arcpy import Parameter, AddMessage
2 | from lib.esri import Geodatabase
3 |
4 | #arcpy toolbox
5 | #parameter indexes
6 | reproject_from_db = 0
7 | reproject_to_db = 1
8 | reproject_projection = 2
9 | reproject_skip_empty = 3
10 |
11 | class Reproject(object):
12 | """arcpy toolbox for reprojecting an entire database"""
13 | def __init__(self):
14 | self.label = 'Reproject Geodatabase'
15 | self.description = """
16 | Reproject an entire geodatabase preserving datasets
17 | and layer names. Useful for reprojecting a geodatabase
18 | or converting from a .mdb or sde to a .gdb"""
19 | def getParameterInfo(self):
20 | return [Parameter(
21 | displayName = 'From Database',
22 | name = 'from_db',
23 | direction = 'Input',
24 | datatype = 'Workspace',
25 | parameterType = 'Required',
26 | ), Parameter(
27 | displayName = 'To Database (Existing features in here will be deleted!)',
28 | name = 'to_db',
29 | direction = 'Input',
30 | datatype = 'Workspace',
31 | parameterType = 'Required',
32 | ), Parameter(
33 | displayName = 'Projection File',
34 | name = 'projection',
35 | direction = 'Input',
36 | datatype = 'DEPrjFile',
37 | parameterType = 'Required',
38 | ), Parameter(
39 | displayName = 'Skip Empty Rows',
40 | name = 'skip_empty',
41 | direction = 'Input',
42 | datatype = 'GPBoolean',
43 | parameterType = 'Optional'
44 | )]
45 | def execute(self, parameters, messages):
46 |
47 | from_db = parameters[reproject_from_db].valueAsText
48 | to_db = parameters[reproject_to_db].valueAsText
49 | projection = parameters[reproject_projection].valueAsText
50 | skip_empty = parameters[reproject_skip_empty].valueAsText
51 |
52 | AddMessage('Tool received parameters: {}'.format(', '.join([p.valueAsText for p in parameters])))
53 |
54 | from arcpy import env, Exists
55 |
56 | if skip_empty == 'true':
57 | env.skipEmpty = True
58 | else:
59 | env.skipEmpty = False
60 |
61 | #run the functions
62 | if not Exists(projection):
63 | AddMessage('Projection file {} does not exist'.format(projection))
64 | return
65 |
66 | # just set the output coordinate system and outputs
67 | # will be projected :)
68 | env.skipAttach = True
69 | env.outputCoordinateSystem = projection
70 |
71 | #call the create datasets function passing the foreach layer function to it
72 | Geodatabase.process_datasets(from_db, to_db, None, None, None)
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/tools/SitemapTool.py:
--------------------------------------------------------------------------------
1 | import arcpy
2 | import csv
3 | import os
4 | import time
5 | from subprocess import Popen
6 | from datetime import date
7 | from lib.esri import Extent, MapDocument
8 | from lib.util.String import get_safe_string
9 | from lib.util.File_Operations import verify_path_exists
10 |
11 | ###
12 | # Esri Toolbox
13 | ###
14 |
15 | # parameter indexes. Change here once and be done
16 | # when updating parameter order
17 | p_layer = 0
18 | p_autocreate_folder = 2
19 | p_export_location = 1
20 | p_buffer_dist = 3
21 | p_title = 4
22 | p_title_field = 6
23 | p_individual = 5
24 |
25 | # get year from date object
26 | date_object = date.today()
27 | year = date_object.strftime("%Y")
28 |
29 | # default tool field values
30 | d_layer = 'Mailing Parcels'
31 | d_export_location = 'N:/PlanningZoning/SiteMaps/{}'.format(year)
32 | d_buffer_dist = '350 Feet'
33 |
34 | # symbol layers
35 | symbols = {
36 | 'red': 'N:/ArcGIS10/Layerfiles/Generic/Red_Boundary.lyr',
37 | 'gray': 'N:/ArcGIS10/Layerfiles/Generic/Gray_Boundary.lyr',
38 | 'orange': 'N:/ArcGIS10/Layerfiles/Generic/Orange_Boundary.lyr',
39 | }
40 |
41 |
42 | class SiteMapGenerator(object):
43 | """
44 | Buffers, selects, and exports a map or set of individual maps
45 | for chosen features, and a csv with the rows.
46 | """
47 | def __init__(self):
48 | self.label = 'Generate Site Maps and CSV'
49 | self.description = """
50 | Generates a site map buffering where necessary
51 | """
52 | self.canRunInBackground = False
53 |
54 | def getParameterInfo(self):
55 | params = [arcpy.Parameter(
56 | displayName='Layer',
57 | name='layer',
58 | direction='Input',
59 | datatype='GPLayer',
60 | parameterType='Required',
61 | ), arcpy.Parameter(
62 | displayName='Export Folder',
63 | name='export_location',
64 | direction='Input',
65 | datatype='DEFolder',
66 | parameterType='Optional',
67 | ), arcpy.Parameter(
68 | displayName='Create output folder if it does not exist?',
69 | name='autocreate_folder',
70 | direction='Input',
71 | datatype='GPBoolean',
72 | parameterType='Optional',
73 | ),arcpy.Parameter(
74 | displayName='Buffer Distance (include units, example: 350 Feet)',
75 | name='buffer_dist',
76 | direction='Input',
77 | datatype='GPString',
78 | parameterType='Optional',
79 | ), arcpy.Parameter(
80 | displayName='Document Title and Export Folder (Insert Document Title into layout)',
81 | name='title',
82 | direction='Input',
83 | datatype='GPString',
84 | parameterType='Optional',
85 | ), arcpy.Parameter(
86 | displayName='Include individual maps of selected features?',
87 | name='individual',
88 | direction='Input',
89 | datatype='GPBoolean',
90 | parameterType='Required',
91 | ), arcpy.Parameter(
92 | displayName='Title Field (field to use as individual map document title)',
93 | name='title_field',
94 | direction='Input',
95 | datatype='GPString',
96 | parameterType='Optional',
97 | )]
98 | params[p_layer].value = d_layer
99 | params[p_export_location].value = d_export_location
100 | params[p_buffer_dist].value = d_buffer_dist
101 | params[p_title_field].filter.type = 'valueList'
102 | params[p_title_field].filter.list = []
103 | params[p_title_field].enabled = False
104 | params[p_individual].value = False
105 | params[p_autocreate_folder].value = False
106 |
107 | return params
108 |
109 | def updateMessages(self, parameters):
110 |
111 | # auto create output folder
112 | if parameters[p_autocreate_folder].valueAsText == 'true':
113 | path = parameters[p_export_location].valueAsText
114 | if not arcpy.Exists(path):
115 | try:
116 | verify_path_exists(path)
117 | parameters[p_export_location].clearMessage()
118 | except Exception as e:
119 | parameters[p_export_location].setErrorMessage('The path you entered is not valid: {}'.format(e))
120 |
121 | # filter the field list
122 | if not parameters[p_layer].hasError() and parameters[p_layer].valueAsText:
123 | parameters[p_title_field].filter.list = [
124 | f.baseName for f in arcpy.Describe(parameters[p_layer].valueAsText).fields]
125 |
126 | # set title field to enabled if we are creating individual maps
127 | parameters[p_title_field].enabled = (
128 | parameters[p_individual].valueAsText == 'true')
129 |
130 | def execute(self, parameters, messages):
131 | """generates the site map pdf and csv files"""
132 |
133 | # set up local vars for easy access
134 | layer = parameters[p_layer].valueAsText
135 | export_location = parameters[p_export_location].valueAsText
136 | buffer_dist = parameters[p_buffer_dist].valueAsText
137 | title_field = parameters[p_title_field].valueAsText
138 | individual = parameters[p_individual].valueAsText
139 | document_title = parameters[p_title].valueAsText
140 | current_document = arcpy.mapping.MapDocument("CURRENT")
141 | data_frame = arcpy.mapping.ListDataFrames(current_document)[0]
142 |
143 | arcpy.AddMessage("""layer={}; export_location={}; buffer_dist={};
144 | title_field={}; individual={}; document_title={};""".format(
145 | layer, export_location, buffer_dist, title_field, individual,
146 | document_title))
147 |
148 | # generate the output workspace
149 | if document_title and document_title != '':
150 | directory = document_title
151 | file_name = get_safe_string(document_title)
152 | else:
153 | directory = time.strftime('%m-%d-%y-(%H.%M)')
154 | file_name = time.strftime('%m-%d-%y-(%H.%M)')
155 |
156 | export_location = os.path.join(export_location, directory)
157 | verify_path_exists(export_location)
158 | arcpy.AddMessage(
159 | 'Creating temp workspace: {}/{}_data.gdb'.format(export_location, file_name))
160 | arcpy.CreateFileGDB_management(
161 | export_location, '{}_data.gdb'.format(file_name))
162 | file_gdb = '{}/{}_data.gdb'.format(export_location, file_name)
163 |
164 | # activate export view
165 | current_document.activeView = 'PAGE_LAYOUT'
166 |
167 | # export the selected features
168 | arcpy.AddMessage(
169 | 'Exporting: layer={}, {}/selected'.format(layer, file_gdb))
170 | arcpy.FeatureClassToFeatureClass_conversion(
171 | layer, file_gdb, 'selected')
172 |
173 | # perform buffer if necessary
174 | if buffer_dist:
175 | # buffer and select features
176 | arcpy.AddMessage(
177 | 'Buffering and Selecting: layer={}, {}/buffer'.format(layer, file_gdb))
178 | arcpy.Buffer_analysis(layer, '{}/buffer'.format(file_gdb),
179 | buffer_dist, 'FULL', 'ROUND', 'ALL')
180 | arcpy.SelectLayerByLocation_management(layer, 'INTERSECT',
181 | '{}/buffer'.format(file_gdb), 0, 'NEW_SELECTION')
182 | buffer = MapDocument.add_map_layer('{}/buffer'.format(file_gdb),
183 | symbols['orange'], '{} Buffer'.format(buffer_dist))
184 |
185 | # export the selected features
186 | arcpy.AddMessage(
187 | 'Exporting: layer={}, {}/buffer_selected'.format(layer, file_gdb))
188 | arcpy.FeatureClassToFeatureClass_conversion(
189 | layer, file_gdb, 'buffer_selected')
190 | buffer_selected = MapDocument.add_map_layer('{}/buffer_selected'.format(file_gdb),
191 | symbols['gray'], 'Buffered Features')
192 |
193 | # add original selection
194 | selected = MapDocument.add_map_layer('{}/selected'.format(file_gdb),
195 | symbols['red'], 'Selected Features')
196 |
197 | # prep output pdf
198 | final_pdf = arcpy.mapping.PDFDocumentCreate(os.path.join(
199 | export_location, '{}_Final.pdf'.format(file_name)))
200 | arcpy.AddMessage('exporting pdf file {}_Final.pdf to {}'.format(
201 | file_name, export_location))
202 |
203 | # activate export view
204 | current_document.activeView = 'PAGE_LAYOUT'
205 |
206 | #zoom to the extent of the buffer
207 | data_frame.extent = Extent.expand(buffer_selected.getExtent(), 10)
208 |
209 | #set the title
210 | current_document.title = document_title
211 |
212 | # clear selection in layers with selected rows
213 | for l in arcpy.mapping.ListLayers(current_document):
214 | try:
215 | if arcpy.Describe(l).fidSet != '':
216 | # throws errors on mosaic layers
217 | arcpy.SelectLayerByAttribute_management(l, "CLEAR_SELECTION")
218 | except:
219 | pass
220 |
221 | # export
222 | MapDocument.export_and_append(export_location, final_pdf)
223 |
224 | rows = []
225 | cursor = arcpy.da.SearchCursor(
226 | '{}/buffer_selected'.format(file_gdb, file_name), ['SHAPE@', '*'])
227 | for row in cursor:
228 | rows.append(row)
229 | if individual == 'false':
230 | continue
231 | # generate the pdf
232 | if title_field in cursor.fields:
233 | current_document.title = row[cursor.fields.index(title_field)]
234 | data_frame.extent = Extent.expand(row[0].extent, 10)
235 | MapDocument.export_and_append(export_location, final_pdf)
236 |
237 | # write out csv rows
238 | arcpy.AddMessage('Exporting csv: {}_output.csv to {}'.format(
239 | file_name, export_location))
240 | # open csv file as binary so it avoids windows empty lines
241 | csv_output = open(os.path.join(
242 | export_location, '{}_output.csv'.format(file_name)), 'wb')
243 | csv_writer = csv.writer(csv_output, delimiter=',',
244 | quoting=csv.QUOTE_ALL, dialect='excel')
245 | csv_writer.writerow(cursor.fields)
246 | csv_writer.writerows(rows)
247 |
248 | # save the outputs
249 | csv_output.close()
250 | final_pdf.saveAndClose()
251 |
252 | # open explorer
253 | Popen('explorer "{}"'.format(export_location.replace('/', '\\')))
254 |
255 | # clean up
256 | del cursor
257 |
--------------------------------------------------------------------------------
/tools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/green3g/esri-python-tools/2f5c428b76b2f96c83803351681624bcc8bbc348/tools/__init__.py
--------------------------------------------------------------------------------