s.geigenbergeresri-de-esridist

Open Streetmap 2 ArcGIS Online mit der ArcGIS API for Python

Blog Post created by s.geigenbergeresri-de-esridist Employee on Dec 14, 2017

Das Beschaffen von Geodaten ist bei vielen Projekten eine zeitaufwendige Arbeit. Dies müssen nicht in jedem Fall hochgenaue Daten sein. Für manche Fragestellungen sind auch Input von Open Streetmap ausreichend. Sei es für Studentenprojekte, sich einen Überblick über eine Thematik zu verschaffen oder eine Zielgruppe mit Informationen zu versorgen. Es besteht die Möglichkeit diese Daten herunterzuladen, in ArcMap oder ArcGIS Pro zu importieren, die gewünschten Daten zu filtern und dann beispielsweise als Dienst zu veröffentlichen. Dieser Weg ist sehr zeitintensiv und daher bedarf es einer Lösung, um die Datenquelle Open Streetmap mit der ArcGIS Plattform direkt zu verbinden, ohne den Umweg über ein Desktopprodukt nehmen zu müssen.

 

 

Zur Umsetzung dieser Idee bietet sich die ArcGIS API for Python an, da diese neben diversen Analysetools auch einen vielseitigen Zugriff auf ArcGIS Online und ArcGIS for Portal anbietet. So wurden basierend auf dieser Technologie verschiedene Python Skripte erstellt und auf GitHub veröffentlicht. Ziel dieses Projekt ist es, dass Daten innerhalb eines festzulegenden Bereichs automatisch als Feature Service veröffentlicht werden.

 

Die Umsetzung erfolgte in zwei Python Skripten, die auch separat genutzt werden können. Bevor der Prozess mit den Daten erfolgen kann, muss der Nutzer jedoch zwei Konfigurationsdateien anpassen. Eine dieser Dateien legt die Konfiguration für die zu veröffentlichenden OSM Daten mittels eines JSON Files fest. Um das Skript starten zu können müssen verschiedene Parameter, wie sie in der Tabelle beschrieben sind, festgelegt werden.

 

 

Neben einer Liste von Kategorien, die in OSM als Paar von Key und Value dargestellt werden, können auch weitere Attribute von den Datensätzen übernommen werden. Zudem müssen eine Bounding Box, sowie die gewünschten Geometrien angegeben werden. Basierend auf diesem Input, wird ein Pythonskript ausgeführt, welches die gewünschten Daten mittels Requests an die Overpass und OSM API abruft.

 

response = api.Get('node[' + category + '](' + minLat + ',' + minLon + ',' 
                               + maxLat + ',' + maxLon + ')', responseformat="json")
            elements = response["elements"]
            for element in elements:
                dictElement = {}
                tags = [element["tags"]]
                tags = tags[0]
                for key_att in attributes:
                    val_att = attributes[key_att]
                    if val_att in tags:
                        dictElement[key_att] = tags[val_att]
                id = element["id"]
                #id = float(id)
                dictElement["id"] = id
                dictElement["lon"] = element["lon"]
                dictElement["lat"] = element["lat"]
                if "user" in attributes or "timestamp" in attributes:
                    try:
                        node = oApi.NodeGet(element["id"])
                        if "user" in attributes:
                            if "user" in node.keys():
                                dictElement["user_"] = node["user"]
                        if "timestamp" in attributes:
                            if "timestamp" in node.keys():
                                dictElement["timestamp"] = node["timestamp"]
                    except:
                        print("Node for this element not available")
                dictElement["attribute"] = key_cat + "-" + val_cat
                dictData.append(dictElement)

 

 Diese Daten werden nun in einem Dictionary strukturiert, in einen pandas Dataframe umgewandelt und zur weiteren Verwendung zurückgegeben.In einem zweiten JSON File werden die Konfigurationen für ArcGIS Online oder ArcGIS for Portal festgelegt.

 

 

Wenn die benötigten Daten vorhanden sind, werden diese auf Korrektheit überprüft. Dies bedeutet, dass eine Verbindung mittels der Login Informationen hergestellt wird. Dies ist nötig, um sicherzustellen, dass die Feature Service ID korrekt ist, wenn dieser überschrieben werden soll. Wenn der Input vollständig und korrekt ist, wird ein weiteres Pythonskript gestartet. Beim Updaten eines Feature Services wird dieser geleert, der Dataframe in Blöcke zerteilt und zu jeweils 100 Features in den Features Service hinzugefügt.

 

listAddFeatures = []
    i = 0
    dataAvailable = True
    dataUploaded = False
    dataQuery = fc_dataAdd.query()
   
    while dataAvailable:
        modulo_i = i % 100
        if modulo_i == 0 and i != 0 and not dataUploaded:
            layer.edit_features(adds = listAddFeatures)
            listAddFeatures.clear()
            dataUploaded = True
            print(str(i)+" Features of "+str(len(dataQuery))+" added.")
        else:
            try:
                listAddFeatures.append(dataQuery.features[i])
                i = i + 1
                dataUploaded = False
            except:
                dataAvailable = False
       
    layer.edit_features(adds = listAddFeatures)
    print("All "+str(len(dataQuery))+" Features added.")

 

Beim Erstellen eines neuen Feature Services fallen vor dem Upload der Daten weitere Arbeitsschritte an, denn es muss ein leerer Feature Service mit den benötigten Felder angelegt werden. Hierfür erstellt man einen neuen Dataframe, welcher nur die Titelzeile des Übergebenen enthält. Nun wird durch alle vorhandenen Felder iteriert und die Felder mit dem Datentyp „int64“ in eine neue Liste hinzugefügt.

 

newField = {
     "name" : intFieldName,
     "type" : "esriFieldTypeInteger",  
     "alias" : intFieldName,
     "sqlType" : "sqlTypeBigInt",
     "nullable" : True,
     "editable" : True,
     "visible" : True
     }
   
token_URL = "{}/sharing/generateToken".format(portal)
token_params = {'username' : user,
      'password' : password,
      'client' : 'referer',
      'referer': portal,
      'expiration': 60,
      'f' : 'json'
      }

r = requests.post(token_URL,token_params)
       
token_obj = r.json()
       
token = token_obj['token']
expires = token_obj['expires']
       
tokenExpires = datetime.datetime.fromtimestamp(int(expires)/1000)
       
featureLayerAdminUrl = layerURL.replace("/rest/", "/rest/admin/")
       
params = {"f":"json", "token":token}
params["addToDefinition"] = json.dumps({"fields":[newField]})
       
layerUpdateUrl = "{}/addToDefinition".format(featureLayerAdminUrl)
layerResult = requests.post(layerUpdateUrl, params)

 

Dieser Schritt ist wichtig, denn beim Veröffentlichen eines Feature Services mittels der ArcGIS API for Python werden Integer Felder nur als „int32“ angelegt und bei höheren Werten treten Fehler auf. Nun wird ein Feature Service erstellt und die „int64“ Felder mittels eines Requests an die Portal API hinzugefügt, denn auf diesem Weg können höhere Integerwerte gespeichert werden.

 

for field in listBigInt:
        del dataframe_total_title[field]
       
fc = gis.content.import_data(dataframe_total_title)
   
item_properties_input = {
    "title": title,
    "tags" : tags,
    "description": description,
    "text": json.dumps({"featureCollection": {"layers": [dict(fc.layer)]}}),
    "type": "Feature Collection",
}

item = gis.content.add(item_properties_input)
new_item = item.publish()

 

Nun ist der Service mit allen benötigten Attributen angelegt und die Features können in Blöcken, wie beim Update, hinzugefügt werden.

 

Ergebnis ist ein GitHub Repository, welches die verschiedenen Skripten enthält. Diese können somit in dem aktuellen Status oder auch einzeln verwendet werden. Zudem ist es möglich die Abläufe an die eigenen Bedürfnisse anzupassen. So könnte man das Skript auch um Methoden erweitern, um die Daten nicht als Feature Service, sondern als Shape File oder Geodatabase zu speichern.

 

Mit diesem Tool ist es gelungen eine Brücke zwischen der reichhaltigen Open Streetmap Datenbasis und der mächtigen ArcGIS Plattform zu schlagen. 

Outcomes