Complete Guide to Python and ArcGIS automated mapping: assigning template sizes

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:

Table 1
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:

Table 2

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 height

4. 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!

Share GIS, not just Python. Huigis essence, pay attention to me and take you to fly! (long press to scan the code)
Note: the second major revision - April 8, 2021
Welcome to exchange Original is not easy, please forward and share more

Difficult article series:

...


Easy and interesting article series:

...

More articles can be searched

Keywords: Python Programming gis arcgis

Added by Digimatt on Fri, 04 Mar 2022 03:09:38 +0200