foto od Kevina Jarretta na Unsplash
Aktualizace 17.3.2025: Ukázkový JSON parser pro Python je přiložen k tomuto článku. Můžete také najít ukázkový parser vytvořený pomocí ArcGIS Pro SDK v GitHub repozitáři ze summitu ArcGIS Developer & Technology 2025.
Chtěli jste někdy vytvořit vlastní analýzu pomocí modelu konektivity z ArcGIS Utility Network, ale měli jste potíže s parsováním JSON souborů, které nástroj Export Subnetwork nebo Trace generuje? Možná máte produkt síťové analýzy, který chcete integrovat s utility network?
Python může být vaším eso v rukávu při vytváření výkonného parseru, který transformuje váš JSON do použitelného grafu, a tento článek vám ukáže jak. S grafem v ruce budete mít svobodu provádět vlastní síťovou analýzu nebo jej použít k naplnění jiného analytického nástroje dle vašeho výběru.
Tento článek má doplnit článek Journey to the Utility Network: Network Integrations publikovaný minulý červen. Abychom co nejlépe využili příklady v tomto článku, doporučuji nejprve přečíst ten článek a mít základní znalosti Pythonu i základní pojmy a terminologii ArcGIS Utility Network. Stránky ArcGIS Pro Python Reference a Utility Network Vocabulary jsou dobrými zdroji, pokud byste potřebovali pomoc.
Nezapomeňte si také stáhnout ukázkový JSON a parser přiložené k tomuto článku, abyste mohli sledovat postup. Tento JSON v článku pochází z modelu Water Utility Network, ale přiložené ukázkové soubory lze přizpůsobit různým datovým modelům nebo verzím. Článek je napsán tak, aby sledoval strukturu přiloženého skriptu, ale můžete použít níže uvedený seznam, abyste viděli, které JSON prvky v souboru jsou v článku diskutovány.
- Source Mapping
- Spatial Reference
- Features
- Connectivity
- Associations
JSON soubor, který budeme v tomto příkladu používat, obsahuje tři hlavní typy výsledků z Export Subnetwork: features, connectivity a associations. Rozhodl jsem se zahrnout geometrie a popisy domén do tohoto exportu, aby byl soubor snazší k pochopení a parsování, ale tyto volby výrazně zvětší velikost výsledného JSON souboru, takže pokud vaše aplikace tyto informace nevyžaduje, měli byste tyto volby nechat nezaškrtnuté.
Načtení souboru
První věc, kterou musíme udělat pro parsování souboru, je načíst JSON soubor z disku do paměti. I když bychom mohli obsah souboru načíst přímo do paměti, je nejlepší použít knihovnu pro parsování JSON jako UJSON k načtení a parsování souboru za nás. To nám umožní pracovat s JSON v souboru pomocí běžných Python datových struktur bez nutnosti starat se o to, jak parsovat všechny složené závorky a uvozovky v souboru. Toto můžete vidět na začátku metody ParseJsonExport.
with open(json_path, "r") as json_file:
json_content = ujson.load(json_file)
del json_file
Jakmile jsme načetli soubor a nechali knihovnu UJSON jej rozparsovat, můžeme pak pracovat s jeho obsahem jako s jakýmkoli jiným Python objektem. Většina prvků v JSON souboru je reprezentována buď slovníky (dictionaries) nebo poli (arrays). Níže vidíte přehled struktury souboru reprezentované v JSON:
{
"connectivity": [
{ 34 },
],
"featureElements": [
{ 34 },
],
"associations": [
{ 34 },
],
"sourceMapping": { 34 },
"spatialReference": { 34 }
}
Pro více informací o tom, jak ovládat výstup souboru tak, aby obsahoval více či méně informací, se podívejte na výše zmíněný článek Utility Network Integrations article. Prozatím pokračujme v analýze toho, jak skript pro parsování využívá každý z těchto prvků.
Source Mapping
Prvek „sourceMapping“ poskytuje slovník (dictionary), který překládá názvy vrstev všech síťových zdrojů ve vaší utility network na interní identifikátor (network source id) spravovaný utility network. To je důležité, protože zbytek JSON souboru odkazuje na prvky podle jejich network source id; pokud tedy chcete vědět, zda je prvek zařízení, vedení nebo struktura a nezahrnuli jste popisy ve svém exportu, musíte se obrátit na tento překlad.
"sourceMapping": {
"1": "UN_134_Associations",
"2": "UN_134_SystemJunctions",
"3": "",
"4": "StructureJunction",
"6": "StructureBoundary",
"7": "StructureJunctionObject",
"5": "StructureLine",
"8": "StructureEdgeObject",
"9": "WaterDevice",
"11": "WaterAssembly",
"12": "WaterJunction",
"14": "WaterJunctionObject",
"10": "WaterLine",
"13": "WaterSubnetLine",
"15": "WaterEdgeObject"
},
Jelikož je prvek source mapping slovníkem (dictionary), jeho využití je jednoduché. Jakmile máme odkaz na tento prvek, můžeme jej snadno použít k překladu network source ID kdykoli je potřeba. Export zahrnutý v tomto příkladu již obsahuje popisy pro všechny naše síťové zdroje; nicméně parser stále používá source mappings k překladu informací v případech, kdy vytvoříte export bez popisů domén.
feature_values['networkSourceName'] = source_mapping.get(element['networkSourceId'], "Unknown")
Nyní když jste viděli, jak používáme source mappings k zpracování prvků, podívejme se na to, jak můžeme použít prvek spatial reference k vytvoření geometrie.
Spatial Reference
Prvek „spatialReference“ popisuje prostorovou referenci utility network, ze které byl export pořízen; je třeba jej brát v úvahu pouze při práci s geometriemi obsaženými v souboru. Prvním krokem je převést prvek spatial reference z JSON do objektu spatial reference pomocí ArcPy.
spatial_reference_element = json_content.get("spatialReference", None)
if spatial_reference_element is not None:
spatial_reference = arcpy.SpatialReference(spatial_reference_element["wkid"])
arcpy.CreateFeatureclass_management(output_gdb,"trace_point","POINT",spatial_reference=spatial_reference)
arcpy.CreateFeatureclass_management(output_gdb,"trace_line","POINT",spatial_reference=spatial_reference)
Note, depending on your release and which method you use to export your JSON the resulting JSON file may not have a spatial reference element. If this is the case, you can always use the spatial reference of the utility network dataset itself using the following code:
un_description = arcpy.Describe(utility_network)
spatial_reference = un_description.spatialReference
Populating these feature classes requires us to parse the features element of the JSON file to extract geometry and attribute information. Conveniently enough, that’s the next thing that our script does.
Features
The “featuresElements” element in the JSON file contains the attribute information for all the features included in your subnetwork or trace. The biggest challenge when interpreting this section of the file is to remember that the network’s representation of your features is more granular than the representation you see in the map. To illustrate, let’s look at an example.
In your map, a junction or single-terminal device is represented as a point feature. In the JSON file, you will find that it corresponds to a single element.
Things get more interesting when you look at a device that has two (or more) terminals. In the map this will appear as a single feature, but in the export, it is represented as multiple elements. The export treats each terminal and terminal path as a separate feature.
This may seem a little unusual at first glance, but its importance is understood once you start comparing the contents of this file with the connectivity portions of the JSON file which reference the individual terminal and edges to establish connectivity. To uniquely identify each point element in the JSON file we could use the network source id, object id, and terminal id of the feature (which would match the connectivity). However, you will notice that in our parser we have chosen to uniquely identify each feature using just the network source id and object id of each feature. This reduces redundancy and means that to look up attribute information you need only a network source id and object id.
feature_key = f"{element['networkSourceId']}${element['objectId']}"
The next difference you will note is in how line features are represented. This is because, in the utility network, each line feature is represented by one or more edge elements. When a line has midspan connectivity then the network uses multiple edge elements to represent each of the sections of the line. You can see this in the example below where the line feature in our geodatabase has two feature elements in the file, each representing a different segment of the line.
To uniquely identify the attributes of each feature, it is sufficient to consider the network source id and object id of the feature. However, to consolidate the geometries for the feature we must consider the from position and to position of each edge because each JSON element represents a subset of the overall line’s geometry. Using the example from above we can see that the first segment accounts for 59% of the shape’s length, with the second segment accounting for the remaining 41%.
Similar to how we handled point features, our parser has chosen to consolidate all the attributes for edge elements into a single feature using the network source id and object id. For the geometries however, we are storing the “from position” of the JSON element along with the coordinates of the line so we can consolidate the geometries from multiple features into a single geometry.
Můžete vidět rozdíl v tom, jak ukládáme geometrie pro bodové a liniové prvky ve výřezu níže.
pro klíč, hodnota v element.items():
pokud klíč == "geometry":
geometry_element = hodnota
pokud "x" v geometry_element:
# Vytvořit bod
geometries[feature_key] = arcpy.Point(geometry_element["x"],geometry_element["y"],geometry_element["z"],geometry_element["m"])
elif "fromPosition" v elementu:
# Přidat geometrii úseku čáry spolu s pozicí v procentech
# Používáme to k pozdějšímu spojení všech úseků čar dohromady
other_geometries = geometries.get(feature_key, [])
other_geometries.append([element["fromPosition"], geometry_element])
geometries[feature_key] = other_geometries
Ačkoli v tomto článku nevysvětlujeme vytváření geometrií, můžete se podívat na metodu CreateGeometries v přiloženém nástroji jako příklad, jak naplnit třídu prvků geometriemi pomocí tohoto exportu. Pokud váš export nepotřebuje používat informace o geometrii, můžete svůj parser zjednodušit a výrazně zmenšit velikost souboru tím, že jednoduše vynecháte geometrie ze svého výstupu a necháte parser je ignorovat.
Konektivita
Prvek „connectivity“ v JSON souboru je neorientovaný graf, který je kolekcí uzlů a hran. Každý prvek spojení popisuje hranu spojující dva uzly, kde atributy from/to JSON prvku odkazují na uzel a atributy via JSON prvku odkazují na hranu.
Konektivita mezi prvky
Přiložený skript ukazuje, jak toho dosáhnout pomocí metody ProcessConnectivity. Tato metoda rozebírá prvky konektivity do jejich samostatných komponent from/via/to pomocí dvou různých přístupů.
- Prvky from a to jsou jednoznačně identifikovány jejich ID zdroje sítě, ID objektu a ID terminálu.
- Prvky via jsou pak jednoznačně identifikovány ID zdroje sítě, ID objektu, pozicí „from“ a pozicí „to“.
pro element v connectivity_element:
from_key = f"{element['fromNetworkSourceId']}${element['fromObjectId']}${element['fromTerminalId']}"
to_key = f"{element['toNetworkSourceId']}${element['toObjectId']}${element['toTerminalId']}"
via_key = f"{element['viaNetworkSourceId']}${element['viaObjectId']}${element['viaPositionFrom']}${element['viaPositionTo']}"
Jakmile jsou všechny prvky from/via/to rozebrány, můžeme tyto informace použít k vytvoření seznamu sousednosti. V případě tohoto parseru považujeme každý prvek from/via/to za vlastní objekt a ukládáme spojení mezi nimi. Tato struktura je vhodná pro analýzu pomocí algoritmu průchodu sítí nebo pro analýzu pomocí Python knihoven jako networkx.
# Uložit konektivitu
from_connections = connectivity.get(from_key, [])
from_connections.append(via_key)
via_connections = connectivity.get(via_key, [])
via_connections.append(to_key)
# Pokud je via spojení asociací konektivity, musíme přidat hrany na obě strany
# Protože nedovolujeme specifikovat digitalizovaný směr u asociace
pokud element["viaNetworkSourceId"] == 1:
to_connections = connectivity.get(to_key, [])
to_connections.append(via_key)
connectivity[to_key] = to_connections
via_connections.append(from_key)
connectivity[from_key] = from_connections
connectivity[via_key] = via_connections
Je důležité si uvědomit, že konektivita v tomto JSON souboru nemá směrnost, tok ani informace o průchodnosti. Jednoduše řečeno, směrnost naznačená pojmenováním from, via a to neznamená skutečný tok v síti utility, ale představuje pořadí vrcholů na linii nebo původ/cíl asociace (což není indikátorem toku). Skutečný tok utility sítě se vypočítává pomocí zdrojů nebo jímek při provádění trasování a momentálně není v JSON výstupu. Příklad toho můžete vidět na obrázku níže, kde některé hrany vypadají jako „otočené“. V této oblasti skutečný tok vody proudí z oblasti s vysokým tlakem napravo do oblasti s nízkým tlakem nalevo, avšak digitalizovaný směr čar na mapě a v JSON ukazuje „from“ konec čar jdoucí zleva doprava.
Prvek konektivity zahrnuje geometrie pro prvky podobně jako prvek features, což znamená, že každý prostorový prvek from/via/to bude obsahovat svou geometrii a prvek geometry edge může odpovídat podmnožině celkové geometrie hrany, pokud hrana obsahuje více hranových prvků, jak je vidět níže.
Nyní když jsme pokryli zpracování prvků a konektivity, poslední zbývající část skládačky je prvek „associations“.
{
"viaNetworkSourceId": 10,
"viaGlobalId": "{29C0B239-9AC0-4E76-9E5A-A912D46CF267}",
"viaObjectId": 22258,
"viaPositionFrom": 0.59016310696272389,
"viaPositionTo": 1,
"viaGeometry": {
"hasZ": true,
"hasM": true,
"paths": [
[
[
1032056.63583742827,
1857139.22013278306,
0.00010000000474974513,
null
],
[
1032024.145088762,
1857269.34913761169,
0.00010000000474974513,
null
]
]
]
},
"fromNetworkSourceId": 12,
"fromGlobalId": "{6F988B32-CA53-4DE2-AA77-A79544386D3F}",
"fromObjectId": 7271,
"fromTerminalId": 1,
"fromGeometry": {
"x": 1032056.63583742827,
"y": 1857139.22013278306,
"z": 0.00010000000474974513,
"m": null
},
"toNetworkSourceId": 12,
"toGlobalId": "{4F36DEA2-656D-468D-B1F9-30849B04FC20}",
"toObjectId": 7723,
"toTerminalId": 1,
"toGeometry": {
"x": 1032024.145088762,
"y": 1857269.34913761169,
"z": 0.00010000000474974513,
"m": null
}
},
Associations
The “associations” JSON element is very similar to the “connectivity” element JSON in structure with the exception that each element consists of only a “from” and “to” element. Additionally, the utility network models associations at the feature level, so when cross-referencing associations to the features element you only need to consider the network source id and the global id of the “from” and “to” feature.
{
"associationType": "containment",
"fromNetworkSourceId": 6,
"fromGlobalId": "{E4693E2A-441D-404F-871F-4D4C5E6AE20A}",
"fromTerminalId": 1,
"toNetworkSourceId": 9,
"toGlobalId": "{FC1540AA-5965-4BDD-B207-F6AD0B40B5BE}",
"toTerminalId": 1,
"fromNetworkSourceName": "StructureBoundary",
"fromTerminalName": "Single Terminal",
"toNetworkSourceName": "WaterDevice",
"toTerminalName": "Single Terminal"
},
By parsing the JSON this way, you can filter out what appear as duplicate associations. Doing this before outputting your results will make it easier to interpret your results as well as reduce the size of any files you output.
{
"associationType": "containment",
"fromNetworkSourceId": 5,
"fromGlobalId": "{8F686F75-3E07-4EFA-BAA4-EAE03F40D93F}",
"fromTerminalId": -1,
"toNetworkSourceId": 10,
"toGlobalId": "{92D69B81-FF17-4C5A-85CB-0733AFE8F02A}",
"toTerminalId": -1,
"fromNetworkSourceName": "StructureLine",
"fromTerminalName": "",
"toNetworkSourceName": "WaterLine",
"toTerminalName": ""
},
… association repeats multiple times …
{
"associationType": "containment",
"fromNetworkSourceId": 5,
"fromGlobalId": "{8F686F75-3E07-4EFA-BAA4-EAE03F40D93F}",
"fromTerminalId": -1,
"toNetworkSourceId": 10,
"toGlobalId": "{92D69B81-FF17-4C5A-85CB-0733AFE8F02A}",
"toTerminalId": -1,
"fromNetworkSourceName": "StructureLine",
"fromTerminalName": "",
"toNetworkSourceName": "WaterLine",
"toTerminalName": ""
},
Note that because we are referencing associations by network source id and global id, while referencing other features by their network source id and object id we will need to do some translation when comparing the different datasets. In this example, the use of the object id was a deliberate choice to make it simpler to select and interact with the geodatabase, but for other applications it may be easier to not use object id for any comparisons and just use the global id.
Conclusion
Now that you have read this article you are equipped with a better understanding of interpreting and parsing the features, connectivity, and associations elements in JSON files produced by the utility network. To inspire you further, attached to this article is a sample Python tool that demonstrates how these techniques can be used to craft your own custom analysis. Happy parsing!