Skip To Content

Пример. Запись запрошенных экстентов карты в класс пространственных объектов

В этом примере показывается, как с помощью ArcGIS REST API и функций геообработки ArcGIS можно считать серверные журналы и создать класс пространственных объектов, содержащий экстенты всех запросов карт, направленных на сервер. Это позволит лучше понять пространственные закономерности, используемые пользователями при просмотре данного сервиса.

Для запуска этого скрипта в ArcGIS for Server необходимо установить ведение журналов не ниже уровня FINE. Затем, после того как сервер выполнит некоторые запросы карт от клиентов, можно будет запустить этот скрипт для записи запрошенных экстентов в класс пространственных объектов. В каждом экстенте из журнала имеется временная метка, которая также записывается в класс пространственных объектов и позволяет воспроизводить запросы в ArcGIS с помощью ползунка времени.

Для работы скрипта требуется существующая файловая база геоданных. Однако он самостоятельно создает класс пространственных объектов.

Пользовательская пространственная привязка (которая не может быть представлена с известным ID или wkid) в данном скрипте не обрабатывается.

# Queries the logs to find the map extents requested for a given map service 
# For output Featureclass creation
print "\nImporting ArcPy..."
import arcpy
# Set Script arguments
arcpy.env.overwriteOutput = True
# For Http calls
import httplib, urllib, json
# For system tools
import sys, datetime, os
# For reading passwords without echoing
import getpass
#Defines the entry point into the script
def main(argv=None):
    # Print some info
    print
    print "This tool is a sample script that queries the ArcGIS Server logs."
    print
    
    # Ask for admin/publisher user name and password
    username = raw_input("Enter user name: ")
    password = getpass.getpass("Enter password: ")
    
    # Ask for server name
    serverName = raw_input("Enter Server name: ")
    serverPort = 6080
    
    # Ask for map service name
    mapService = raw_input("Enter map service name, using a forward slash / to denote a folder: ")
    
    if mapService.endswith(".MapServer"):
        pass
    else:
        mapService += ".MapServer"
    
    # Ask for output workspace
    outputWorkspace = raw_input("Enter output Workspace (Geodatabase location): ")
    
    # Ask for output featureclass name
    outputFeatureclass = raw_input("Enter output Featureclass name: ")
    
    # Construct REST service URL
    serviceURL = "/arcgis/rest/services/{0}".format( mapService.replace( ".", "/"))
    
    # Get a token
    print "Requesting Token..."
    token = getToken(username, password, serverName, serverPort)
    if token == "":
        print "Could not generate a token with the username and password provided."
        return
    # Get Extent detail for service
    print "\nLooking up Service details..."
    serviceURL = serviceURL + "/?Token=" + token
    fullExtent = getFullExtent( serverName, serverPort, serviceURL)
    
    if not fullExtent:
        return 
       
    # Construct URL to query the logs
    logQueryURL = "/arcgis/admin/logs/query"
    logFilter = "{'services': ['" + mapService + "']}"
    
    # Supply the log level, filter, token, and return format
    params = urllib.urlencode({'level': 'FINE', 'filter': logFilter, 'token': token, 'f': 'json'})
    
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
    
    # Connect to URL and post parameters
    print "Accessing Logs..."
    httpConn = httplib.HTTPConnection(serverName, serverPort)
    httpConn.request("POST", logQueryURL, params, headers)
    
    # Read response
    response = httpConn.getresponse()
    if (response.status != 200):
        httpConn.close()
        print "  Error while querying logs."
        return
    else:
        data = response.read()
        
        # Check that data returned is not an error object
        if not assertJsonSuccess(data):
            print "  Error returned by operation. " + data
        else:
            print "  Operation completed successfully!"
        
        # Deserialize response into Python object
        dataObj = json.loads(data)
        httpConn.close()
        
        # Open Insert Cursor on output
        output = openCursor( outputWorkspace, outputFeatureclass, fullExtent[ "spatialReference"][ "wkid"])
        
        if not output:
            return
        
        # Need this variable to track number of events found for ExportMapImage call
        logEvents = 0
        
        # Need Array to hold Shape
        shapeArray = arcpy.Array()
        
        # Iterate over messages
        for item in dataObj[ "logMessages"]:
            eventDateTime = datetime.datetime.fromtimestamp( float( item[ "time"]) / 1000)
            
            if item[ "message"].startswith( "Extent:"):
                eventScale = None        # Scale
                eventInvScale = None    # Inverse-Scale
                eventWidth = None        # Width
                eventHeight = None    # Height
                
                # Cycle through message details
                for pair in item[ "message"].replace(" ", "").split( ";"):
                    if pair.count( ":") == 1:
                        key, val = pair.split( ":")
                        
                        # Pick out Extent
                        if key == "Extent" and val.count( ",") == 3:
                            # Split into ordinate values
                            MinX, MinY, MaxX, MaxY = val.split( ",")
                            MinX = float( MinX)
                            MinY = float( MinY)
                            MaxX = float( MaxX)
                            MaxY = float( MaxY)
                            
                            # Make sure extent is within range
                            if MinX > fullExtent[ "xmin"] and MaxX < fullExtent[ "xmax"] and MinY > fullExtent[ "ymin"] and MaxY < fullExtent[ "ymax"]:
                                shapeArray.add( arcpy.Point( MinX, MinY))
                                shapeArray.add( arcpy.Point( MinX, MaxY))
                                shapeArray.add( arcpy.Point( MaxX, MaxY))
                                shapeArray.add( arcpy.Point( MaxX, MinY))
                                shapeArray.add( arcpy.Point( MinX, MinY))
                        
                        # Pick out Size
                        if key == "Size" and val.count( ",") == 1:
                            eventWidth, eventHeight = val.split( ",")
                            eventWidth = float( eventWidth)
                            eventHeight = float( eventHeight)
                        
                        # Pick out Scale
                        if key == "Scale":
                            eventScale = float( val)
                            eventInvScale = 1 / eventScale
                
                # Save if Shape created
                if shapeArray.count > 0:
                    # Create new row
                    newRow = output.newRow()
                    
                    # Add Shape and Event Date
                    newRow.setValue( "Shape", shapeArray)
                    newRow.setValue( "EventDate", eventDateTime)
                    newRow.setValue( "Scale", eventScale)
                    newRow.setValue( "InvScale", eventInvScale)
                    newRow.setValue( "Width", eventWidth)
                    newRow.setValue( "Height", eventHeight)
                    
                    output.insertRow( newRow)
                    
                    # Clear out Array points
                    shapeArray.removeAll()
                    
                    logEvents += 1
        
        print "\nDone!\n\nTotal number of events found in logs: {0}".format( logEvents)
        
        return
#A function to query service for Extent and Spatial Reference details
def getFullExtent( serverName, serverPort, serviceURL):
    # Supply the return format
    params = urllib.urlencode({'f': 'json'})
    
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
    
    # Connect to URL and post parameters
    httpConn = httplib.HTTPConnection(serverName, serverPort)
    httpConn.request("POST", serviceURL, params, headers)
    
    # Read response
    response = httpConn.getresponse()
    if (response.status != 200):
        httpConn.close()
        print "Error while querying Service details."
        return
    else:
        data = response.read()
        
        # Check that data returned is not an error object
        if not assertJsonSuccess(data):
            print "Error returned by Service Query operation. " + data
        
        # Deserialize response into Python object
        dataObj = json.loads(data)
        httpConn.close()
        
        if not 'fullExtent' in dataObj:
            print "Unable to find Extent detail for '{0}'!".format( serviceURL)
            print dataObj
        elif not 'spatialReference' in dataObj[ 'fullExtent']:
            print "Unable to find Spatial Reference for '{0}'!".format( serviceURL)
            print "dataObj"
            
        else:
            return dataObj[ 'fullExtent']
    
    return
#A function to create new Featureclass and return an Insert Cursor, used to store Map Query Extents.
def openCursor( workspace, featureclassName, srid):
    if not arcpy.Exists( workspace):
        print "Unable to find Workspace '{0}'...".format( workspace)
        return
    
    print "Creating output Featureclass..."
    arcpy.CreateFeatureclass_management( workspace, featureclassName, "POLYGON", None, None, None, srid)
    
    Featureclass = workspace + os.sep + featureclassName
    
    print "  Adding field(s)..."
    arcpy.AddField_management( Featureclass, "EventDate", "DATE", None, None, None, None, "NULLABLE", "NON_REQUIRED")
    arcpy.AddField_management( Featureclass, "Scale", "DOUBLE", 19, 2, None, None, "NULLABLE", "NON_REQUIRED")
    arcpy.AddField_management( Featureclass, "InvScale", "DOUBLE", 19, 12, None, None, "NULLABLE", "NON_REQUIRED")
    arcpy.AddField_management( Featureclass, "Width", "LONG", 9, None, None, None, "NULLABLE", "NON_REQUIRED")
    arcpy.AddField_management( Featureclass, "Height", "LONG", 9, None, None, None, "NULLABLE", "NON_REQUIRED")
    
    print "  Opening Insert Cursor..."
    return arcpy.InsertCursor( Featureclass)
#A function to generate a token given username, password and the adminURL.
def getToken(username, password, serverName, serverPort):
    # Token URL is typically http://server[:port]/arcgis/admin/generateToken
    tokenURL = "/arcgis/admin/generateToken"
    
    # URL-encode the token parameters
    params = urllib.urlencode({'username': username, 'password': password, 'client': 'requestip', 'f': 'json'})
    
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
    
    # Connect to URL and post parameters
    httpConn = httplib.HTTPConnection(serverName, serverPort)
    httpConn.request("POST", tokenURL, params, headers)
    
    # Read response
    response = httpConn.getresponse()
    if (response.status != 200):
        httpConn.close()
        print "Error while fetching tokens from admin URL. Please check the URL and try again."
        return
    else:
        data = response.read()
        httpConn.close()
        
        # Check that data returned is not an error object
        if not assertJsonSuccess(data):
            return
        
        # Extract the token from it
        token = json.loads(data)
        return token['token']
        
#A function that checks that the input JSON object
# is not an error object.
def assertJsonSuccess(data):
    obj = json.loads(data)
    if 'status' in obj and obj['status'] == "error":
        print "Error: JSON object returns an error. " + str(obj)
        return False
    else:
        return True
# Script start
if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))