Select to view content in your preferred language

How to programatically add symbols to a .stylx (Styles) file and apply that symbology to a feature layer

8230
7
03-27-2020 03:41 PM

How to programatically add symbols to a .stylx (Styles) file and apply that symbology to a feature layer

I was recently working on a project that needed something like 100 unique symbols. Each symbol was a circle or square with a four letter code on it. This is something that lent itself perfectly to being done programatically. I really didn't want to have to generate those symbols one by one, so I started searching of ways to do this programatically, but didn't find much information on how I might get this done using Python.

The first glimmer of hope was when I found how to apply symbols from style gallery (i.e. Favorites or project Styles .stylx). I had accepted the fact that I'd just programatically create a bunch of png's, add them to my Favorites, and then write some code to apply the symbols in my Pro project. Then I stumbled upon an Esri blog post that mentioned that .stylx files are just SQLite databases. This got me thinking that I'd be able to further program the generation of these symbols.

I used DB Browser for SQLite to take a look at the structure of the .stylx db. Most of what you need is stored in the ITEMS table. There's a content field where the json representation of the symbol is stored. For some reason, the json string has a character at the end that makes DB Browser think it's a blob instead of text. If you delete the 00 at the end, it will then display as text and you can get an idea of what the json looks like for a CIMPointSymbol for example.

Things to know about the .stylx file:

  • both ID and KEY have unique constraints, so make sure the rows you add to the database comply with these constraints
  • CLASS is the type of things your adding to the .stylx - I didn't investigate all the options, but I do know 1 is a color and 3 is a symbol

I broke my code up into two python scripts. Here's the pseudocode and some real code for each script:

Generate symbols - programmatically generate symbols and add to your Favorites .stylx

  • read symbol information from excel file into a pandas df (things like fill color, border color, text for symbol, etc.)
  • read in a template json from a text file - instead of generating all the json, I just modify this template by changing things like fill color, border color, etc
    • I created this template by creating a symbol in my Favorites.stylx, viewing it in DB Browser, copying the json, and saving it to a .json file
  • connect to the .stylx using sqlite3 (conn= sqlite3.connect(db))
  • iterate through the rows in my df
    • modify my json from the template
    • create a new row to add to the db
      • new_row = (new_id,3,'',code,'rgb;black',json.dumps(new_sym),code)‍‍‍‍‍
        execute sql code to insert row into db
      • cursor.execute('INSERT INTO ITEMS(ID, CLASS, CATEGORY, NAME, TAGS, CONTENT, KEY) VALUES(?,?,?,?,?,?,?)', new_row)
        commit changes to db and close db

At this point, I now have a bunch of new symbols in my .stylx file. While testing I worked on a copy of my Favorites.stylx, but as long as Pro is closed, I don't think there's any issues with working on the real file in its default location ("C:\Users\userxxxx\AppData\Roaming\ESRI\ArcGISPro\Favorites.stylx"). Just make sure you have a backup copy of it just in case you ruin it.

Apply symbols - as long as the name of your new symbols matches the symbol item values in your Pro project, it's relatively easy to apply the symbology programmatically

  • read in map, layer, symbology from a Pro project
    • m = p.listMaps('Map')[0]
      lyr = m.listLayers("layer name")[0]
      sym = lyr.symbology
      
  • iterate through symbol groups and symbol items in each group and apply symbol, as long as there is symbol in the gallery with the name of your item's value, this should work
    • for grp in sym.renderer.groups:
          for item in grp.items:
          item.symbol.applySymbolFromGallery(item.values[0][0])‍‍‍‍‍‍
  • set lyr.symbology to the sym that was just modified (I forgot to do this and it took me forever to figure out why my symbology wasn't getting applied)
  • save the project

I hope this helps someone that found themselves in the same position that I was. If anyone needs any more details on how this was done, just let me know.

Here's a list of posts that I found while searching how to do this

Importing Custom Point Symbols 

Custom Symbols for AGOL 

Creating a .style file 

Assigning an icon from a .stylx file and rotating it, in arcpy 

Comments
HollyBaun1
Occasional Contributor

Thank you for sharing this! I have about 70 unique polygon symbols in a layer and have been trying to find an efficient way to get them into a style. I probably could have manually saved each symbol to a style in the time I spent searching for a different way, but I know I have other symbols/layers in a similar situation. I hope to try your method.

JimmyKnowles
Frequent Contributor

Holly, glad this was helpful for you. Let me know if you run into any roadblocks if you try this out.

krisDupond
New Explorer

Hi,

I believe your solution approaches is what I need after a lot of research (Pro 2.7.1) which include apply symbols from style gallery, and examples like Restaurant or Python_CIM_Access. Your method seems to be the only current solution to my problem but I am not sure that it is the right and only possible way. So I would like more details or just advices.

But in the previous cases (links) it is often a question of superimposing symbols and not of automatically creating a symbol with several layers as my goal is to automatically create a legend (.lyrx) of 500 polygon symbols which have each 5 layers. Each polygon has a five characters tag identifier (eg "A1234"). Each character determines the symbology of a layer according its place in the idientifier : the first character fix the solid color of the first layer, the second is used for colored hatch of the second layer, the last three layers have a single and different text marker (like "T" or whatever). The hatches and markers are colored and rotated according to the color and angles read in an attribute table (colors, angles).

This table can therefore be read with pandas df (Python). But I have a few questions in the part "Generate symbols" : 1) will the template need to be created with a single 5-layer symbol (.lyrx), then renamed to .json? 2) I don't understand after "Connect to .stylx like using sqlite3" what ".stylx" is refering about (the new .json ?) ? 3) Is it really possible not to start with a military style .lyrx (MIL *)? 4) Do you think my problem might actually be solved with your method? 5) How should I proceed to program the “Generate symbols” part as automatic as possible?

Thanks for your advices.

Regards

 

krisDupond
New Explorer

Forgot the "links" :

apply symbols from style gallery, and examples like Restaurant or Python_CIM_Access_Samples

@

HeinrichB
New Contributor

I am desperately looking for a way to convert Symbology Encoding to ArcGIS Pro .Stylx do you have any idea for me? 

KarstenRank
Frequent Contributor

Hi,

great way to create styles from images in minutes. Thanks a lot for the saved time! 

RanaMuhammadRameez
Occasional Explorer

Thanks a lot, Jimmy!

You helped a lot.

I have created this script to load PNGs from a directory to a style file. Hope, It helps someone.

 

import os
import base64
import sqlite3
import json

# Paths to symbols and new style file
symbol_directory = r"Path\To\PNG\Symbols"
stylx_path = r"Path\To\Styles\symbols.stylx"

# Initialize the .stylx file
if not os.path.exists(stylx_path):
    open(stylx_path, "w").close()  # Create the file if it doesn't exist

print("Connecting Style DB")
# Connect to the .stylx file as a SQLite database
conn = sqlite3.connect(stylx_path, timeout=60)  # Timeout of 10 seconds

cursor = conn.cursor()
print("Connected.")
# Prepare the table for symbols
cursor.execute(
    """
    CREATE TABLE IF NOT EXISTS ITEMS (
        ID INTEGER PRIMARY KEY AUTOINCREMENT,
        CLASS INTEGER,
        CATEGORY TEXT,
        NAME TEXT,
        TAGS TEXT,
        CONTENT TEXT,
        KEY TEXT
    )
"""
)

print("Table initiated if not existed.")
print("Inserting symbols into db...")
# Insert symbols from directory into .stylx
for file_name in os.listdir(symbol_directory):
    if file_name.endswith(".png"):
        symbol_name = os.path.splitext(file_name)[0]
        image_path = os.path.join(symbol_directory, file_name)

        # Read and encode the image file
        with open(image_path, "rb") as img_file:
            img_data = img_file.read()
            encoded_image = base64.b64encode(img_data).decode("utf-8")

        # Define the symbol JSON structure (simple CIMPictureMarker)
        new_symbol_json = {
            "type": "CIMPointSymbol",
            "symbolLayers": [
                {
                    "type": "CIMPictureMarker",
                    "path": image_path,
                    "title": symbol_name,
                    "url": f"data:image/png;base64,{encoded_image}",
                    "size": 20,
                    "enable": "true",
                    "colorLocked": "true",
                    "anchorPointUnits": "Relative",
                    "dominantSizeAxis3D": "Y",
                    "billboardMode3D": "FaceNearPlane",
                    "invertBackfaceTexture": "true",
                    "scaleX": 1,
                    "textureFilter": "Draft",
                }
            ],
        }

        # Generate unique ID and insert into the .stylx file
        new_row = (
            3,  # CLASS (symbol class)
            "",  # CATEGORY
            symbol_name,  # NAME
            "Landmark Symbol",  # TAGS (optional)
            json.dumps(new_symbol_json),  # CONTENT (symbol JSON)
            symbol_name,  # KEY
        )
        cursor.execute(
            # "INSERT INTO ITEMS(ID, CLASS, CATEGORY, NAME, TAGS, CONTENT, KEY) VALUES(?,?,?,?,?,?,?)",
            "INSERT INTO ITEMS(CLASS, CATEGORY, NAME, TAGS, CONTENT, KEY) VALUES(?,?,?,?,?,?)",
            new_row,
        )
        print(f"Inserted symbol: {symbol_name}")

# Commit and close the connection
conn.commit()
conn.close()
print("All symbols have been added to the .stylx file.")
Version history
Last update:
‎12-12-2021 03:51 AM
Updated by: