Select to view content in your preferred language

Programmatically update vector tile layer style with Python API

793
1
04-17-2019 06:38 AM
Jay_Gregory
Frequent Contributor

Is there a way to programmatically update a vector tile layer style with the Python API yet?  I looked but couldn't seem to find one. 

I have created a "copy" of a vector tile layer in my Portal, such that the item information page give me the ability to download the style, or update the style with a new root.json file, but I can't seem to update a new root.json programmatically via the Python API.  Doing it via the REST API I guess is possible, but I took a look at the update item form in the REST API and it looks complicated it's not readily apparent what I would need to do to just update the root.json file...

Thanks!

1 Reply
Matthew_Muehlhauser
Regular Contributor

In case anyone comes across this now, there is a way to do this. When you do a "save as" for a vector tile layer, it creates a new item in your contents. However; this item is identical to the original and still uses the same root.json, info.json, and sprites as the original vector tile layer. It's not until you go into the vector tile style editor and hit save that it will create new resources for this vector tile layer. So, to do this programmatically, you need to do the same thing and add those 6 resources with the new references to your new vector tile layer. I've included a bit of script if you wanted to start from a VTPK on your computer or if you want to search an existing item in AGOL (DISCLAIMER: I have run this in a notebook and this is a condensed version of my own, so there may be some bugs that need to be fixed before it runs properly)

 

import os
from time import time
from itertools import repeat
from urllib.request import urlretrieve
import json
from arcgis.gis import GIS

############ HELPER FUNCTIONS #########################

def prepare_info_json(x, original_url, new_url, timestamp):
    if x == "../../metadata.json":
        return x
    return x.replace("../fonts", f"{original_url}/resources/fonts").replace("../sprites", f'{new_url}/resources/sprites').replace("sprites/sprite", f"sprites/sprite-{timestamp}").replace(" ", "%20")


#######################################################

if __name__ == "__main__":
    # This script is meant for new VTPK uploads.
    # If there are huge changes to the layers / symbology
    # Otherwise, you can just replace the existing VTPK / Service

    vtpk_path = "C:\\Path\\To\\Your\\VTPK.vtpk"

    # Need to do it this way so can get a token for later
    # gis = GIS("https://www.arcgis.com/", "USERNAME", "PASSWORD", profile="some_profile_name")
    gis = GIS(profile="some_profile_name")
    token = gis._con.token

    item_properties = {
        "tags": "Your, Tags, Here",
        "description": "Some Description of the Service",
        "snippet": "Your Summary",
        "title": "title"
    }
    vtpk_item = gis.content.add(item_properties, data=vtpk_path)
    #vtpk_item = gis.content.search("", item_type="Vector Tile Layer")[0] # Assuming a unique match
    vtpk_layer = vtpk_item.publish()

    data = vtpk_layer.layers[0].styles

    timestamp = int(time() * 1000)
    copy_item = vtpk_layer.copy(title="New Item Title")
    resources = copy_item.resources

    new_url = f"https://www.arcgis.com/sharing/rest/content/items/{copy_item.itemid}"
    temp_file_location = os.path.expanduser("~\\Desktop")

    # Create the new info root.json
    info_list = list(map(prepare_info_json, copy_item.layers[0].info, repeat(vtpk_item.url), repeat(new_url), repeat(timestamp)))
    resource_info = {"resourceInfo": info_list}

    info_json = f"{temp_file_location}/infoRoot.json"
    with open(info_json, "w", encoding='utf8') as f:
        json.dump(resource_info, f, ensure_ascii=False)

    # Creat the new styles root.json
    data["sprite"] = f"{new_url}/resources/sprites/sprite-{timestamp}"
    data["glyphs"] = data["glyphs"].replace("..", f"{vtpk_item.url}/resources")
    data["sources"]["esri"]["url"] = vtpk_item.url
    data["sources"]["esri"]["tiles"] = [vtpk_item.url + "/tile/{z}/{y}/{x}.pbf"]

    style_json = f"{temp_file_location}/styleRoot.json"
    with open(style_json, "w", encoding='utf8') as f:
        json.dump(data, f, ensure_ascii=False)

    # Save the sprite sheets since they don't change, just a bit of naming later
    extensions = [".png", ".json", "@2x.png", "@2x.json"]
    base_sprite_url = f"{vtpk_layer.url}/resources/sprites/sprite"
    for ext in extensions:
        urlretrieve(f"{base_sprite_url}{ext}?token={token}", f"{temp_file_location}/sprite{ext}")

    resources.add(f"{temp_file_location}/sprite.png", "sprites", f"sprite-{timestamp}.png")
    resources.add(f"{temp_file_location}/sprite.json", "sprites", f"sprite-{timestamp}.json")
    resources.add(f"{temp_file_location}/sprite@2x.png", "sprites", f"sprite-{timestamp}@2x.png")
    resources.add(f"{temp_file_location}/sprite@2x.json", "sprites", f"sprite-{timestamp}@2x.json")
    resources.add(style_json, "styles", "root.json")
    resources.add(info_json, "info", "root.json")

    files_to_remove = [
        f"{temp_file_location}/sprite.png",
        f"{temp_file_location}/sprite.json",
        f"{temp_file_location}/sprite@2x.png",
        f"{temp_file_location}/sprite@2x.json",
        style_json,
        info_json
    ]
    for f in files_to_remove:
        os.remove(f)

 

 

You have the option to update the style json in the style section in this script by updating the data["layers"], or you can update it after the fact with another script and it would be as simple as getting the item, getting the resource manager / resources, and then updating the source.

 

copy_item.resources.update(style_json, "styles", "root.json")