Create a new map in ArcGIS Pro with arcpy

3396
7
08-11-2017 12:57 PM
Status: Open
Labels (1)
RonnieRichards
Regular Contributor

In Pro 2.0 it does not appear it is possible to create a new map within the project. Please consider adding a method to the ArcGISProject object with the ability to create a new map with a name from arcpy.

Alternatively a copy map method on the existing Map object would be a workable alternative 

You can copy/paste maps manually in a project so this functionality seems like a necessary addition for additional project automation. 

7 Comments
RyanCooper5

This would be a welcome feature. In my case, I'd like to be able to pass a list of map names to a method that would generate new maps with the names from that list. For example using a hypothetical newMap() method.

aprx = arcpy.mp.ArcGISProject("CURRENT")
map_list = ['map1', 'map2', 'map3']
for m in map_list:
    aprx.newMap(m)
for m in aprx.listMaps():
    print("Map: " + m.name)

# Result
# Map: map1
# Map: map2
# Map: map3‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In this example, 3 new maps (map1, map2, and map3) would be added to the current project.

RyanCooper5

This is the kind of process that would be nice to be able to automate:

Manual creation of several new maps in ArcGIS Pro

EglantinaDani

It would be very useful to have the command. I wonder if there is a way to do it without waiting for the future versions.

EricTran

I'm making a check to create a new map if the user is running an aprx without a map in it.  Creating a new map when the script detects this would be very helpful.

MaxSquires

it's not much to look it but:

#!/usr/bin/env python3
"""
    make aprx file, 'add' some maps to it:

    #comtypes:
    #one of the following installed libraries are required:
    #C:\Program Files (x86)\ArcGIS\Desktop10.7\com
    #or
    #C:\Program Files\ArcGIS\Server\com
"""

import arcpy
import tempfile
import os
import shutil
from shutil import make_archive


out_dir = os.environ["USERPROFILE"] + os.sep + "Desktop"

aprx_name_maps_in_pro = "aprx_with_maps"

## new empty map names:
map_list = ["map1", "map2", "map3"]

#from an empty project (may be able to minimize these items even futher
# but this does what i need.
cimdoc = """<CIMDocumentInfo xsi:type='typens:CIMDocumentInfo' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:typens='http://www.esri.com/schemas/ArcGIS/2.4.0'>
<Version>2.4.0</Version><Build>19948</Build><DocumentTitle>New</DocumentTitle><SavePreview>false</SavePreview>
<UseRelativePath>true</UseRelativePath><Antialiasing>esriBGLAntialiasingNone</Antialiasing>
<TextAntialiasing>esriBGLTextAAliasForce</TextAntialiasing></CIMDocumentInfo>"""


#from an empty project
gisproj = """<CIMGISProject xsi:type="typens:CIMGISProject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:typens="http://www.esri.com/schemas/ArcGIS/2.4.0"></CIMGISProject>"""


#from an empty project rb in python 3.
EMPTY_PROJECT_BYTES = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'


def installComTypes():
    """
        install comtypes in virtaul env at runtime, why not.
    """

    install_com_types_string = """import subprocess
import sys

def install(package):
    subprocess.call([sys.executable, "-m", "pip", "install", package])

    install('comtypes')

    """


    insall_scrpit_name = "install_comtypes"


    install_file = insall_scrpit_name + ".py"

    if os.path.exists(install_file):
        os.remove(install_file)

    with open(install_file, "w") as w_install:
        w_install.write(install_com_types_string)


    import install_comtypes

   
#or not
#hack number 1
installComTypes()
import comtypes

def CreateMXD(path):
    GetModule('esriCarto.olb')
    import comtypes.gen.esriCarto as esriCarto
    #print("dir esriCarto:")
    #print(dir(esriCarto))
    pMapDocument = CreateObject(esriCarto.MapDocument, esriCarto.IMapDocument)
    pMapDocument.New(path)
    #print("dir pMapDocument:")
    #print(dir(pMapDocument))
    #print("dir pMapDocument.map:")
    #print(dir(pMapDocument.Map))
    pMapDocument.Save() #probably not required...


def GetLibPath():
    """ Get the ArcObjects library path

        It would be nice to just load the module directly instead of needing the path,
        they are registered after all... But I just don't know enough about COM to do this

    """

    thisCompath=os.path.join(arcpy.GetInstallInfo()['InstallDir'],'com')
    desktopCompath = r"C:\Program Files (x86)\ArcGIS\Desktop10.7\com"
    serverCompath = r"C:\Program Files\ArcGIS\Server\com"
   
    if os.path.exists(thisCompath):
        return thisCompath
    elif os.path.exists(desktopCompath):
        return desktopCompath
    elif os.path.exists(serverCompath):
        return serverCompath
    else:
        raise Exception("#must use desktop or server com libriary (or copy the com folder to a known location")

def GetModule(sModuleName):
    """ Generate (if not already done) wrappers for COM modules
    """

    from comtypes.client import GetModule
    sLibPath = GetLibPath()
    GetModule(os.path.join(sLibPath,sModuleName))
   
def CreateObject(COMClass, COMInterface):
    """ Creates a new comtypes POINTER object where
        COMClass is the class to be instantiated,
        COMInterface is the interface to be assigned
    """

    ptr = comtypes.client.CreateObject(COMClass, interface=COMInterface)
    return ptr


def create_binary_file(_bytes, file_path):
    """
    Create an 'empty' project file
    """

    with open(file_path, 'wb') as fh:
        fh.write(_bytes)

def create_xml_file(xml_string, file_path):
    """
        create an xml file with string of input
    """

    with open(file_path, "w") as xml:
          xml.write(xml_string)

#temporary workspace (emptied at end)
tempDir = tempfile.mkdtemp()


#write xml to documentinfo.xml
create_xml_file(cimdoc, os.path.join(tempDir, "DocumentInfo.xml"))



#write xml to gisproject.xml
create_xml_file(gisproj, os.path.join(tempDir, "GISProject.xml"))



#hack number 2 write binary data to 007Index.ind
create_binary_file(EMPTY_PROJECT_BYTES, os.path.join(tempDir, "007Index.ind"))

#template pro map name
empty_pro_map = "aprx_empty"

#list of required files for creating aprx file
files = ["007Index.ind","GISProject.xml", "DocumentInfo.xml"]

#zip the files one at a time
for f in files:
    shutil.make_archive(os.path.join(out_dir, empty_pro_map), "zip", tempDir)

#empty ArcGISProject
new_aprx_path = os.path.join(out_dir, empty_pro_map) + ".aprx"

#rename the .zip file to .aprx
os.rename(os.path.join(out_dir, empty_pro_map + ".zip"), new_aprx_path)

#  everything below here requires python 3 arcpy
#  you could split this code here.

#  load aprx
aprx = arcpy.mp.ArcGISProject(new_aprx_path)




for m in map_list:
    map_path = os.path.join(tempDir, m + ".mxd")
    if os.path.exists(map_path):os.unlink(map_path)
    #hack number 3
    CreateMXD(map_path)

    existing_maps = aprx.listMaps()
   
    aprx.importDocument(map_path)
    existing_maps_now = aprx.listMaps()
    for ma in existing_maps_now:
        if ma not in existing_maps:
            ma.name = m

#output
aprx.saveACopy(os.path.join(out_dir, aprx_name_maps_in_pro + ".aprx"))

#clear temp files (comment the next line out if you want to review the temp files)
shutil.rmtree(tempDir)
print(tempDir)
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

save as:

<%path%>\add_maps_to_new_aprx.py

run it like:

C:\Program Files\ArcGIS\Pro\bin\Python\Scripts>propy.bat <%path%>add_maps_to_new_aprx.py

the associated toolboxes and geodatabases are created on initial launch, but you could set those if you want.

PaulDziemiela2

I think as Ryan demonstrates we would like access to the functionality of the catalog window, much of which is willy-nilly missing.  Max gets points for causing my eyes to bleed staring at the CIM guts, but if that's the answer then Pro is not the right question.

Paul

MaxSquires

haha. yeah, that is kind of overkill.  alternatively you can have an empty template mxd file that you use and an empty aprx file and just:

templateMxd = "C:/admin/temp_mxd.mxd"
aprx = arcpy.mp.ArcGISProject("C:/admin/temp_aprx.aprx")
map_list = ["map1", "map2", "map3"]

for m in map_list:
    
    existing_maps = aprx.listMaps()
    
    aprx.importDocument(templateMxd)
    existing_maps_now = aprx.listMaps()
    # not going to assume these maps are being added at any one index position
    for ma in existing_maps_now:
        if ma not in existing_maps:
            # set the name of the imported map
            ma.name = m

# done adding new maps to aprx, save it
aprx.saveACopy("C:\admin\aprx_with_new_maps.aprx")