Python Blog

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

Other Boards in This Place


Latest Activity

(210 Posts)
DanPatterson
MVP Esteemed Contributor

The license level for this tool is Advanced.  To bad.

Read more...

more
0 0 108
DanPatterson
MVP Esteemed Contributor

Ramble

I have a separate miniconda environment nicely set up with the versions of what I want without constraints of what is "pinned" by the arcgispro-py3 environment or its clone.

Sure, you can clone, or make a stripped down environment by using arcpy-base, or if you have that streak of adventure, you can push "pip" ing and such.  Sadly many of those options dont work for me, since I tend to do my "Arc..." stuff separate from my "NumPy ... " stuff.  So here is a demo of what you can do if you only need a simple or quick access to python code that you have and you don't want to mess with the existing Arcgis Pro setup

 

Working with packages

This is a demo to show how to use your own modules within the arcgis pro environment. The modules that you need to import have to be identified, then imported.

  1. Identify the location for the package/module/script you want to import
  2. Do any required imports.

Once you have done your imports, your package/module/script can be used within ArcGIS Pro's notebooks, python windows and tools.

packages_02.pngUsing the python window do a simple import and get a list of the folders that Pro searches looking for python packages etcetera.

In my example, I installed Arcgis Pro on my machine locally in C:\Arcpro_36.

 

 

 

Screenshot 2026-02-04 181749.png

Now, I wanted to add paths to the location where I save my packages.  The simple script to the right shows one way.

 

 


packages_05.pngYou can also continue working there in either a notebook or the python window.  In the example to the right, the project, active map and a layer list was obtained.

packages_06.pngpackages_07.png

 

 

 

 

Now, I imported my own module to convert it to a numpy array, obtain some information and produce a "map"/"plot" of the data

 

packages_08.pngpackages_09.png

I use this approach to keep my Pro and python/numpy environments clean and in the versions that I want.  But keep it in mind if you want to keep your work organized and separate.

 

more
2 1 374
CarlosLuna
Occasional Contributor

Good Day to All,

I'm a long time developer first time Esri Developer and I wanted to share a little python script i wrote that would get all the featureclasses within a database and export them to CSV. As I ran into the issue where there was no easy way to obtain the metadata via SQL Queries. So I decided to use the python API calls with arcpy.

 

Also I separated the files, one gets all of the feature classes (title only and some descriptions) but then the second file is to get the metadata of each feature class itself -- you must view its metadata (saved as an XML) directly. In the end I saved the results into a CSV.

Let me know if stuff like this is useful for here. I have also written python code to get data out of our organization's ArcGIS Online (i can write a separate post about this at a later time).

 

Code found here also:

 

https://github.com/lunasnest/Arcpy/tree/main/get_feature_classes

 

 

Cheers

more
3 2 518
HaydenWelch
MVP Regular Contributor

After a year of procrastination, I've finally written a script that makes the CIM module sane:

https://github.com/hwelch-fle/cimple

 

The goal here is to generate a new cim module from the existing one with the added benefit of proper typing of dependent attributes and string literal completion. The overall structure and usability of the CIM is also improved by utilizing dataclasses and removing the original inheritance hierarchy in favor of direct attribute definition.

Example:

# Original CIM definition
class CIMChart():
    """
      Provides access to members that control chart properties.
    """
    def __init__(self, *args, **Kwargs):
        self.name = str()
        self.series = []
        self.generalProperties = 'CIMChartGeneralProperties'
        self.legend = 'CIMChartLegend'
        self.axes = []
        self.mapSelectionHandling = ChartMapSelectionHandling.Highlight
        self.metaData = str()
        self.multiSeriesChartProperties = 'CIMMultiSeriesChartProperties'
        self.enableServerSideProcessing = False
        self.chartType = ChartType.Basic

# cimple CIM definition
@dataclass
class CIMChart:
    """https://github.com/Esri/cim-spec/blob/main/docs/v3/CIMCharts.md#cimchart-1

    Provides access to members that control chart properties.
    """
    name: str = ''
    series: list[Any] = dc_field(default_factory=list[Any])
    generalProperties: CIMChartGeneralProperties = dc_field(default_factory=lambda: CIMChartGeneralProperties())
    legend: CIMChartLegend = dc_field(default_factory=lambda: CIMChartLegend())
    axes: list[Any] = dc_field(default_factory=list[Any])
    mapSelectionHandling: ChartMapSelectionHandling = 'Highlight'
    metaData: str = ''
    multiSeriesChartProperties: CIMMultiSeriesChartProperties = dc_field(default_factory=lambda: CIMMultiSeriesChartProperties())
    enableServerSideProcessing: bool = False
    chartType: ChartType = 'Basic'

 

The dc_field factories replace the GetPythonClass call and allow for mutable defaults. There are still some lists and dictionaries that use the Any type since I couldn't infer their type from the original module.

 

Update: 

I would like to pose a question to the community:

When using CIM, do you care about inheritance hierarchy? As in, do you use isinstance checks on the bases of a desired CIM class?

e.g.

x = obj.getDefinition('V3')

if issubclass(x, cim.CIMBasicFeatureLayer):
    ## do thing if Layer type
    ...
elif issubclass(x, cim.CIMDisplayTable):
    ## do thing if Table type
    ...

 

Or do you mostly just target the expected output class

e.g.

x = obj.getDefinition('V3')

if isinstance(x, cim.CIMFeatureLayer):
    ## do thing if Layer instance
    ...
elif isinstance(x, cim.CIMMapTableView):
    ## do thing if Table instance
    ...

 

Currently, this module removes all inheritance from CIM and treats each class as a distinct set of keys. This means keys are duplicated between classes, but I feel that when writing code this is much easier to read as you don't need to traverse a whole hierarchy when referencing the source:

HaydenWelch_0-1767376427979.png

Original CIM with inheritance on right, flat cimple.cim on left

If I was to re-introduce subclass and instance checks, I would likely do so by comparing the keys of the classes:

def __subclasscheck__(self, other: object) -> bool:
    if hasattr(other, '__dataclass_fields__'):
        return set(type(other).__dataclass_fields__).issubset(set(type(self).__dataclass_fields__))
    return super().__subclasscheck__(other)

 

Since most of the time when you're type narrowing CIM, you're really just looking for a specific set of keys, I think this is the most sensible solution. It will likely diverge from the base CIM inheritance model though since simple classes that have only simple attributes like 'name' and 'type' will now be considered to be related.

 

Alternatively, I could re-write the script to honor the inheritance hierarchy, with the drawback that inheritance may cause some weird issues with circular imports (the reason the cim module uses strings is to defer this to runtime). Since right now no classes inherit from any other cim class, it is safe to defer the typing and defaults to runtime where all classes are loaded in and can be initialized.

 

Update 2:

I've gone ahead and implemented a CIMBase metaclass that lives in its own _base.py file:

class CIMBase(type):
    def __subclasscheck__(cls, subclass: type) -> bool:
        return set(getattr(cls, '__dataclass_fields__', 'a')).issubset(set(getattr(subclass, '__dataclass_fields__', 'b')))
    
    def __instancecheck__(self, instance: object) -> bool:
        return issubclass(type(instance), self)

 

All CIM classes will now inherit this metaclass to get attribute based (duck style) instance/subclass checks:

@dataclass
class CIMFeatureLayer(metaclass=CIMBase):
    """https://github.com/Esri/cim-spec/blob/main/docs/v3/CIMVectorLayers.md#cimfeaturelayer-1

    NO DOC
    """
    ...
    
@dataclass
class CIMGeoFeatureLayerBase(metaclass=CIMBase):
    """https://github.com/Esri/cim-spec/blob/main/docs/v3/CIMVectorLayers.md#cimgeofeaturelayerbase-1

    Represents a layer that draws geographic feature data using a renderer.
    """
    ...
    
>>> isinstance(CIMFeatureLayer(), CIMGeoFeatureLayerBase)
True
>>> issubclass(CIMFeatureLayer, CIMGeoFeatureLayerBase)
True

 

Again, this will not reveal the original class hierarchy, but will instead reveal if the checked class (arg 1) has all the attributes defined in arg 2.

 

Update 3:

I've now implemented cimple.conversion that allows conversion between cim, cimple, and json. There are some weird caveats for testing (cim objects can take a string literal or an enumerated integer, so writing a test for that required checking against the enum name attribute). Here's the simple code for the conversion functions:

def cim_to_json(cim_object: object, indent: int=4) -> str:
    """Convert a CIM object into a JSON string"""
    return json.dumps(cim_object, indent=indent, cls=JSONCIMEncoder)

def json_to_cim(cim_json: str) -> Any:
    """Convert a json string into an initialized CIM object"""    
    return json.loads(cim_json, cls=JSONCIMDecoder)

def cim_to_cimple(cim_obj: Any) -> Any:
    if isinstance(cim_obj, list):
        return [cim_to_cimple(o) for o in cim_obj]
    cimple_obj = getattr(cim, cim_obj.__class__.__name__, None)
    if cimple_obj:
        return cimple_obj(**{k: cim_to_cimple(v) for k, v in cim_obj.__dict__.items()})
    return cim_obj

def cimple_to_cim(cimple_obj: Any) -> Any:
    if isinstance(cimple_obj, list):
        return [cimple_to_cim(o) for o in cimple_obj]
    cim_obj = getattr(arcpy_cim, cimple_obj.__class__.__name__, None)
    if cim_obj:
        # CIM objects from the arcpy.cim module cannot be initialized with values
        # We need to initialize the object then update the instance __dict__
        cim_obj = cim_obj()
        cim_obj.__dict__.update({k: cimple_to_cim(v) for k, v in cimple_obj.__dict__.items()})
        return cim_obj
    return cimple_obj

 

I've excluded the JSONCIMDecoder an JSONCIMEncoder classes, but they are exposed in the module root if you need to subclass them for custom encodings. 

more
4 3 991
Clubdebambos
MVP Regular Contributor

Learn how to build a custom ArcGIS Pro script tool that creates rectangular buffers around point features using ArcPy based on user input or from field attributes.

Read more...

more
2 0 1,186
DanPatterson
MVP Esteemed Contributor

Not for most, but it is one option.

Read more...

more
2 0 965
DanPatterson
MVP Esteemed Contributor

This is a follow up to the Common points blog, but with floating point issues addressed because sometimes exact comparisons aren't great due to calculation artifacts or user input issues.

Read more...

more
3 0 777
DanPatterson
MVP Esteemed Contributor

A followup to 'Common segments', since it the points that count in the first place.  I will compare 'normal' to 'numpy' checks so you can add to your arsenal.

Read more...

more
1 1 744
DanPatterson
MVP Esteemed Contributor

Polygons have them.  Polylines sometimes.  It isn't just about matching coordinates.  It is about sharing commonalities.

Read more...

more
1 0 658
DanPatterson
MVP Esteemed Contributor

A combination of ideas.

Read more...

more
2 0 607
259 Subscribers
Labels