Complete Guide to Python and ArcGIS automated mapping (3): assigning templates
In the second chapter of the guide, we made three mxd templates of different sizes to adapt to different drawing units. For example, the mxd template adapted to Zigong City is not suitable for Guangyuan City (Guangyuan City has a large area). How to let the computer assign templates of appropriate size to different drawing units is the problem to be solved in this chapter. |
1. Standardization of template information
The standardized matching information is generated according to the name of the mxd template file made in Chapter 2. This information can assist the program to match the mxd template with appropriate size for each drawing unit.
Why standardize template information?
Because the program can't know the size of the mxd template you make, or which size corresponds to different templates.
So we need to save the template information into the program to let the program know that there is such a template.
You can enter the name of each template and its corresponding size before the program runs, but it's too troublesome. What if there are many templates? What if you make a mistake?
Therefore, we need to let the computer know the size of the template, which requires us to strictly follow the provisions of Chapter 2: mxd template is named according to * * width x height mxd * * come on.
For example: 1080x700 mxd,1080x1300.mxd,1180x900.mxd (in millimeters).
The program will automatically identify and collect width and height data to reduce manual operation.
The collected information style is as follows: it represents pagesize1, and the English character represents sizes 1080 and 700; pagesize2 indicates 1080 and 1300; pagesize3 represents 1180 and 900.
{ "pagesize1":(1080,700), "pagesize2":(1080,1300), "pagesize3":(1180,900) }
Note: there is a kind of information in Python Programming dictionary that readers will not see here. |
According to the template name in the input folder, the code C1 (... / main/part1.py) to generate the above standard matching information is as follows:
# -*- coding:utf-8 -*- # ---------------------------------------------------------- # Author: LiaoChenchen # Created on: 2021/1/29 17:34 # ---------------------------------------------------------- from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function from __future__ import division import arcpy import os from os.path import splitext as st from os import listdir as ld def size_creator(path): """ Generate a dictionary in standard format according to the input template file address. :param path: {String} mxd file path :return: """ print("Please make sure there are no irrelevant files in the template folder!") m_dict = {} counter = 1 for m_name in [st(x)[0] for x in ld(path) if ".mxd" or ".MXD" in x]: width, height = m_name.split("x") name = "pagesize{}".format(counter) m_dict[name] = (int(width), int(height)) counter+=1 return m_dict
Last returned m_dict is the standardized matching information, which will be used later in the program.
2. Minimum boundary geometry
Now that we know the size of the template, the next step must be to obtain the size of the drawing unit. In order to adapt to each other.
For example, there are 17 prefecture level cities and 17 mapping units in the figure below. How can we know their wide and long sides?
Then we can use the minimum boundary geometry tool to help us determine.
Minimum boundary geometry - when the area is the smallest, it can completely cover the closed surface of the polygon. As shown in the following figure: the light green box is the minimum rectangular boundary geometry of each drawing unit.
Generating the minimum boundary geometry can directly calculate the long and short edges,
By calling the ArcPy method ArcPy MinimumBoundingGeometry_ Management can calculate and generate the minimum boundary geometric layer, which has two fields, MBG_Width and MBG_Length field, the former represents the short side and the latter represents the long side.
Note: This tool is located in data management tool - > feature - > minimum boundary geometry; Official online documents: https://desktop.arcgis.com/zh-cn/arcmap/10.3/tools/data-management-toolbox/minimum-bounding-geometry.htm |
The code C2 (... / main/part1.py) for generating the minimum boundary geometry and its related operations using the MappingIndex layer is as follows:
# -*- coding:utf-8 -*- # ---------------------------------------------------------- # Author: LiaoChenchen # Created on: 2021/1/29 17:34 # ---------------------------------------------------------- from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function from __future__ import division import arcpy import os from os.path import splitext as st from os import listdir as ld """_______global_values_______""" # address mxd_template = "tempMXDS" # Template file location output_dir = "out" # Drawing output location gdb_path = "arcpy guide.gdb" # Database address # Important constant FIELD = "CITY" # Search field MI = "MappingIndex" # Drawing index file name SCALE = 200000 # Cartographic scale """_______global_values_______""" arcpy.env.overwriteOutput = True arcpy.env.workspace = gdb_path def check_field(layer, field): """ Check whether a feature file exists field field """ fields = arcpy.ListFields(layer) return field in [_.name for _ in fields] class PageSizeMatch(object): """ Fit page size """ def __init__(self, feature_name, field, mxd_template_d): """ :param feature_name: {String} # The name of the drawing key layer :param field: {String} field = "CITY" # Search field :param mxd_template_d: {Dict} Template matching information (Dictionary) """ self.f = feature_name # The name of the drawing key layer self.field = field self.m_d = mxd_template_d self.minimum_bounding() # Make minimum geometric boundary layer true_height = self.check_width_height() # Get real altitude information # Update height information into the mapping index layer (MappingIndex) self.update_width_height(true_height) self.update_page_size(SCALE) def minimum_bounding(self): """ 1.make MinimumBoundingGeometry feature layer 2.add PAGRSIZE field :return: """ if not check_field(MI,"MBG_Width"): #▶ Note 1 ◀ #▶ Note 2 ◀ mbe = arcpy.MinimumBoundingGeometry_management mbe(self.f, "m_out", "ENVELOPE", "LIST", self.field, True) print("Complete MinimumBoundingGeometry") arcpy.Delete_management(self.f,"FeatureClass")#▶ Note 3 ◀ arcpy.Rename_management("m_out",self.f,"FeatureClass") if not check_field(self.f,"PAGESIZE"): #▶ Note 4 ◀ # The minimum geometric boundary has not been calculated print('Add field PAGESIZE') arcpy.AddField_management( self.f, "PAGESIZE", "TEXT", field_length = 100)
Because the code is cut from the complete code, there are some temporarily irrelevant code fragments.
Let's look at * * check first_ Field * * and minimum_ The bounding method.
The former is located in the first part of the above code fragment and is used to check whether a field exists in a vector feature file.
The latter is located at the end of the above code fragment and is used to calculate and generate the minimum boundary geometry and its related operations.
▶ Note 1 ◀:
Custom check is used_ The field method checks whether the MappingIndex (see Chapter 2 of the guide for the origin of the layer) has MBG_Width field, because the generated minimum boundary geometry layer automatically creates MBG_Width field.
Therefore, you can check the field to confirm whether the minimum has been executed_ Using the bounding method, the minimum boundary geometric layer is generated.
▶ Note 2 ◀:
Functions in Python are also objects. We use a short variable mbe to point to arcpy MinimumBoundingGeometry_ The memory address of the management method, and then use mbe () to execute the function.
Avoid writing too long a line of code.
▶ Note 3 ◀:
Replace layer.
First delete the original MappingIndex vector file, and then rename the generated minimum boundary geometry vector data to "MappingIndex". This is used to replace the MappingIndex layer in the mxd template file (replace its source file with the minimum boundary geometry layer).
▶ Note 4 ◀:
Add the field PAGESIZE.
After performing the above operations, reopen property sheet 1 of MappingIndex:
Note: MBG_Width and MBG_Length is in meters. |
3. Update MBG_Width and MBG_Length field
——Transfer MBG_Width and MBG_ The length field is updated to the relationship between width and height!
After the second step, we obtained the MBG_Width and MBG_Length of each drawing unit. The MBG of each drawing unit can be seen from table 1_ Width is greater than MBG_ Length.
However, the map shape cannot be completely distinguished only by the short and long sides.
It is impossible to distinguish between recumbent and long strip, as shown below. Can you tell which of the following figures is correct? Based only on the short side and long side information, the following two are correct. But there is only one answer.
If you can't distinguish between these two cases, you can't allocate mxd templates with appropriate size ratio.
Therefore, only when we know whether the short side is high or wide can we know that it is correct.
How do you know if the short side is high or wide?
When turning a face into a point, a rectangle will generate 5 points, which are lower right corner, upper right corner, upper left corner, lower left corner and lower right corner (overlapping with the first point). The difference between the y coordinates of the lower right corner and the upper right corner is the height of the rectangle.
If the difference is equal to MBG_ If the width field value is, then the short side is high. The second case in the figure above is correct. Conversely, the difference is equal to MBG_ If the length field value and the long side is high, the first case is correct.
Feature break point to point through featureverticestopoints in ArcPy_ Management method implementation.
The code C3 (... / main/part1.py) identifying the height and width is as follows:
# -*- coding:utf-8 -*- # ---------------------------------------------------------- # Author: LiaoChenchen # Created on: 2021/1/29 17:34 # ---------------------------------------------------------- from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function from __future__ import division import arcpy import os from os.path import splitext as st from os import listdir as ld class PageSizeMatch(object): """ ... Omit some codes """ def check_width_height(self): #▶ Note 1 ◀ # Use the "feature break point to point feature" tool to convert the minimum boundary geometry into points; # Each minimum boundary geometry will generate 5 points: lower left, upper left, upper right, lower right, lower left; # The difference between the first two points is the height # Dictionary with return value of height: {prefecture level city name: height} fea_v = "feature_vertices" # feature name short_f = arcpy.FeatureVerticesToPoints_management short_f(self.f, fea_v, "ALL") cursor = arcpy.da.SearchCursor( fea_v, [self.field, "SHAPE@Y"] ) cursor2l = [(x[0],x[1]) for x in cursor] # Convert to list del cursor #▶ Note 2 ◀ cursor2l_1, cursor2l_2 = cursor2l[::5], cursor2l[1::5] height_info = {} for i in xrange(len(cursor2l_1)):#▶ Note 2 ◀ height = cursor2l_2[i][1] - cursor2l_1[i][1] height_info[cursor2l_2[i][0]] = abs(height) return height_info #▶ Note 4 ◀ def update_width_height(self, height_infomation): """ This method will "MBG_Width","MBG_Length"The two fields are updated to width and height :param height_infomation: {Dict} Contains the height information of the drawing unit :return: """ _field = [self.field,"MBG_Width", "MBG_Length"] with arcpy.da.UpdateCursor(self.f, _field) as cursor: for row in cursor: name, width, height = row #▶ Note 5 ◀ if round(height_infomation[name], 2)==round(width, 2): row[1] = height row[2] = width cursor.updateRow(row) print("update width&height completly!")
The * * C2 * * code in the PageSizeMatch class is omitted.
▶ Note 1 ◀:
check_width_height uses the feature break point to point feature tool to convert the minimum boundary geometry into points.
This method completes the work of turning point to point, and calculates the height of each drawing unit at the same time.
▶ Note 2 ◀:
Each rectangle will generate 5 points arranged in order. cursor2l[::5] use slices to take the first one every 5; cursor2l[1::5] take the second slice every five.
▶ Note 3 ◀:
This for loop combines the y-axis difference and the drawing unit name into a dictionary.
▶ Note 4 ◀:
height_ The information parameter receives the above check_ width_ The return value of the height method. This method will put the MBG in the MappingIndex layer_ Width and MBG_ The value of length is updated to width and height, respectively.
▶ Note 5 ◀:
If the short side is equal to the height, the field MBG_Width and MBG_ The meaning of length changes from the original short side and long side to width and height, then the original MBG_Width and MBG_ The value of length needs to be interchanged.
Updated effect:
The property table of MappingIndex layer is shown in the following table:
Comparing Table 2 with table 1, we can find the MBG of many cities_ MBG of width_ Length has been interchanged. For example, Zigong City
Border map of Zigong City: Zigong City is obviously recumbent, with width greater than height4. Update PAGESIZE
After successfully completing the first three steps of this chapter, the next step is to update the PAGESIZE field.
In the first part, we get the information of mxd template.
In the second and third parts, the accurate width and height data of each drawing unit are successfully obtained.
Then the next step is to compare the two sets of data and assign an mxd template of appropriate size to each drawing unit.
Finally, the matching result is stored in the PAGESIZE field.
The code * * C4 * * (... / main/part1.py) for matching and PAGESIZE field updating is as follows:
# -*- coding:utf-8 -*- # ---------------------------------------------------------- # Author: LiaoChenchen # Created on: 2021/1/29 17:34 # ---------------------------------------------------------- from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function from __future__ import division import arcpy import os from os.path import splitext as st from os import listdir as ld def select_template_size(size, template_size): """ :param size: Composition of list height and width such as:[659.8490915000066, 822.3146429999917] :param template_size: Drawing template size :return: Returns the name (key) of the drawing template size, If no suitable drawing template can be found, return -1 """ map_w, map_h = size[0], size[1] map_div = map_w / map_h # A dictionary that matches the template size of the drawing unit template_size_fit = { k:(v[0], v[1], v[0]*v[1], v[0]/v[1]) for k,v in template_size.items() if v[0]>map_w and v[1]>map_h } #▶ Note 6 ◀ d_len = len(template_size_fit) # Dictionary to list d2l = zip(template_size_fit.keys(), template_size_fit.values()) # Sort by the third number in the tuple (by area) d2l_sorted = sorted(d2l, key=lambda x: x[1][2]) if d_len > 2: #▶ Note 7 ◀ two_remaind = d2l_sorted[:2] # (u'pagesize3', (1380, 850, 1173000, 1.6235294117647059)) res = min(two_remaind, key=lambda x: abs(x[1][3]-map_div)) return res[0] # u'pagesize3' elif d_len==2: res = d2l_sorted[0] return res[0] elif d_len==1: return d2l_sorted[0][0] else: # info = "there are drawing units exceeding the page size" return -1 class PageSizeMatch(object): """ ... Omit some codes """ def update_page_size(self,scale): """ Update fill field "PAGESIZE" Value of :param scale: {Int} Scale size :return: """ _field = ["MBG_Width", "MBG_Length", "PAGESIZE"] with arcpy.da.UpdateCursor(self.f, _field) as cursor: for row in cursor: row_p = [row[0], row[1]] new_row = [x/scale*1000 for x in row_p] # PAGESIZE1 or -1 pgs_name = select_template_size(new_row, self.m_d) if pgs_name != -1: p_size = self.m_d[pgs_name] # (1180, 900) # update PAGESIZE field values 1180x900 #▶ Note 8 ◀ row[2] = "{}x{}".format(p_size[0],p_size[1]) else: print("There are drawing units that exceed the size of all template pages") cursor.updateRow(row) print("update PAGESIZE completly!")
Class method update_page_size is used to update the PAGESIZE field.
The specific matching method is selected_ template_ Size method. The method receives two parameters, one is a list composed of width and height (unit: mm), and the other parameter is the template standardization information (Dictionary) obtained in the first step.
The internal idea is: first, select the mxd template that can accommodate the drawing unit.
If there are more than three mxd templates that meet the conditions, click ▶ Note 7 ◀ As you can see in, the area indicator will be enabled, sorted by size, and the smallest two templates will be selected.
Then use the * * width height ratio * * index to select the mxd template whose width height ratio is closest to the drawing unit from the two templates.
▶ Note 6 ◀:
Dictionary derivation to generate such a dictionary: {..., "Dazhou city": (width, height, area size, width height ratio),...}.
... v[0]>map_ w and v[1]>map_ h... This code makes a preliminary screening and selects the mxd template that meets the width and height of the drawing unit. At the same time, area and width height ratio are introduced here, which are used for further judgment and screening.
▶ Note 8 ◀: Update the PAGESIZE field in the form of 1180x900.
Updated PAGESIZE field:
The updated PAGESIZE field value is the template size assigned to each drawing unit.
summary
This chapter is mainly divided into four parts.
- Get template data information.
- Use the minimum boundary geometry to confirm the long and short edges.
- Identify whether the long and short sides correspond to height or width, and finally confirm whether the minimum boundary geometry is strip or recumbent.
- Compare the width and height of each drawing unit with the template standard information, and finally select the appropriate template for each drawing unit, and the results are stored in the PAGESIZE field of the MappingIndex layer.
Then the next chapter goes to using ArcPy to control mxd automatic update and drawing.
Note: see the download file for the complete and detailed code of this chapter/ main/part1.py |
Concluding remarks
Free download full set of resources:
- Presentation file data
- source code
- Guide document booklet for easy computer viewing
Pay attention to official account reply: automatic mapping, get all downloads!
Note: the second major revision - April 8, 2021 |
Difficult article series:
- Complete Guide to Python and ArcGIS automated mapping ——A total of six chapters, with tutorial source code, data and documentation manual download.
- In depth cartographic representation ——The mechanism, application and Realization of cartographic expression are deeply studied.
- Advanced skills of ArcGIS mapping ——ArcGIS advanced mapping skills, discover various mapping and beautification skills unknown to ArcGIS. (constantly updating!)
- Automatic mapping of ArcGIS(ArcPy) multi process based on Python ——Use multi process to quickly plot in large quantities.
- Creating your first GIS program in Python ——Easy to understand, hands-on teaching you to build your first GIS program in Python.
...
Easy and interesting article series:
- Discover the world from maps ——From the map, I can find the strange, beautiful and my world. (constantly updating!)
...
More articles can be searched