Select to view content in your preferred language

Create Bookmarks from Features

1917
9
06-14-2023 02:01 PM
JohannesLindner
MVP Frequent Contributor

@JoeBryant1 posted an Idea earlier about being able to create bookmarks from the features in a feature class / layer. It then got merged into this really old Idea: https://community.esri.com/t5/arcgis-pro-ideas/layer-to-bookmarks-tool-amp-bookmarks-to-layer/idc-p/...

 

Seeing as the original Idea is from 2011, I don't think this will be implemented any time soon, but it seemed like a fun little project to implement myself...

The function in the following Python script will read a feature class or layer (honoring definition queries and selection) and create a bookmark file using the feature extents. The bookmark file can then be imported into an ArcGIS Pro map.

  • The bookmarks can be named by a field or with an incrementing number.
  • The features can be dissolved by a field (in that case the naming field gets switched to the dissolve field).
  • The features can be buffered.
  • You can specify the minimum length of the smallest dimension of the resulting bookmarks.

Using this function, it's easy to turn the script into a script tool, which is attached to this post (as zipped atbx).

 

This was a fun little project, and if it can help  some people, all the better 🙂

 

 

import arcpy
from pathlib import Path


def _create_bookmark_dict(in_geometry, min_dist):
    """Returns a dict defining a bookmark for the input arcpy.Geometry object."""
    e = in_geometry.extent
    d = min([e.width, e.height])
    if d < min_dist:
        buffered_geometry = in_geometry.buffer((min_dist - d) / 2)
        e = buffered_geometry.extent
    return {
        "type": "CIMBookmark",
        "location": {
            "xmin": e.XMin,
            "xmax": e.XMax,
            "ymin": e.YMin,
            "ymax": e.YMax,
            "spatialReference": {"wkid": in_geometry.spatialReference.factoryCode}
            }
        }
    
def create_bookmarks(in_features, out_path, name_field=None, dissolve_field=None, buffer_dist=None, min_dist=None):
    """Creates bookmarks from all features in a layer.
    in_features (path to feature class as str / arcpy.mp.Layer object): The input features. If this is a layer, definition queries and selection will be honored.
    out_path (str): Path of the output bkmx file.
    name_field (str): The field used to name the bookmarks. Optional, default is incrementing number.
    dissolve_field (str): The field by which to dissolve the features before creating the bookmarks. Optional, by default there is no dissolving. If both name and dissolve field are specified, the name will be ignored and the features will be named by the dissolve field.
    buffer_dist: distance by which the input features are buffered, measured in the default units of the in_feature's coordinate system, important for small features. Optional, defaults to zero.
    min_dist: minimum extent width or height, measured in the default units of the in_feature's coordinate system, important for small features. Optional, defaults to zero.
    
    """
    # dissolve features
    if dissolve_field not in [None, "", "#"]:
        in_features = arcpy.management.Dissolve(in_features, "memory/dissolve", [dissolve_field])
        if name_field not in [None, "", "#"]:
            name_field = dissolve_field
    # buffer features
    if buffer_dist is not None and buffer_dist > 0:
        in_features = arcpy.analysis.Buffer(in_features, "memory/buffer", buffer_dist)
    # create the bookmarks
    if min_dist is None or min_dist < 0:
        min_dist = 0
    bookmarks = []
    read_fields = ["SHAPE@"]
    if name_field not in [None, "", "#"]:
        read_fields.append(name_field)
    for i, row in enumerate(arcpy.da.SearchCursor(in_features, read_fields)):
        try:
            bm = _create_bookmark_dict(row[0], min_dist)
        except:  # eg null geometry
            continue
        if name_field not in [None, "", "#"] and row[1] is not None:
            bm["name"] = row[1]
        else:
            bm["name"] = str(i)
        bookmarks.append(bm)
    # write bkmx file
    out_dict = {"bookmarks": bookmarks}
    f = Path(str(out_path))
    f.write_text(str(out_dict))



if __name__ == "__main__":
    in_features = arcpy.GetParameter(0)
    out_path = arcpy.GetParameterAsText(1)
    name_field = arcpy.GetParameterAsText(2)
    dissolve_field = arcpy.GetParameterAsText(3)
    buffer_dist = arcpy.GetParameter(4)
    min_dist = arcpy.GetParameter(5)
    create_bookmarks(in_features, out_path, name_field, dissolve_field, buffer_dist, min_dist)

 

 

 


Have a great day!
Johannes
9 Replies
JoeBryant1
Frequent Contributor

Wow! Mad props, Johannes! I feel like you wrote that code in the same amount of time it took me to write the original post!

Just tested and it works as expected. I'll be adding it to our company tool list.

THANKS!

0 Kudos
hgnudas
Emerging Contributor

Hello,

Thanks so much for creating this tool. I am having some issues when attempting to create a map series with the script. The script creates the correct number of bookmarks from the name, however, the extent is completely off. All of my points are within Ontario, however, once I run the tool all of the outputted bookmarks jump to the middle of the ocean near Sardigna and Barcelona. I presume it is something to do with the projection, but I can't seem to figure it out. Thanks in advance.

0 Kudos
hgnudas
Emerging Contributor

I figured it out! It had to do with the coordinate system in my map properties being incorrect. After updating, all facility points now have a associated bookmark!

0 Kudos
Trisarahtops
Emerging Contributor

I've been looking for this exact functionality, thanks so much Johannes! I tweaked the code slightly to allow a negative buffer distance, because I'd like the bookmark to be centered and zoomed in to the feature. Works great!

0 Kudos
TCKauhi
Occasional Contributor

@JohannesLindner that worked great, thank you!

0 Kudos
FrankHerbert
Occasional Contributor

@JohannesLindner  This is brilliant, thank you for sharing 🙂

0 Kudos
Welch_Nicolas
Emerging Contributor

I'm running into a strange issue where certain fields will not work as the bookmark name. In those cases the script appears to run but the resulting .bkmx file will not load and/or is empty. When I chose other fields (or no field) I get all the right bookmarks, but without the right name they are very hard to use. The field in question appears to be exactly like others that I tried that ran successfully, with one exception: despite having an alias, the field does not show up with its alias in the Name Field dropdown even when "Show Field Aliases" is checked.

0 Kudos
Welch_Nicolas
Emerging Contributor

Finally solved it. Apparently the script did not like field values with em dashes in them. It tolerated all the other symbols but when I excluded those features I got a successful result.

Thanks for this great tool.

0 Kudos
ODWC_GIS
Frequent Contributor

I love this script!  But I, too, was having some problems with the imported bookmark locations being in odd locations.  So I opened up the tool's python file (specifically, tool.script.execute.py) and saw a misspelling in line 17.  After changing "spatielReference" to "spatialReference," and zipping it all back together, it works without any issues whatsoever!

I really can't thank you enough for creating this toolbox and sharing it with the rest of us.  This has probably saved my rump from from just hitting a brick wall ("Weil, yeh cain't get there from here.").

0 Kudos