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!
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")