geo_polygon_to_s2cells()

Calcola i token di cella S2 che coprono un poligono o un multipolygon sulla Terra. Questa funzione è uno strumento di join geospaziale utile.

Sintassi

geo_polygon_to_s2cells(poligono [,level[,radius]])

Altre informazioni sulle convenzioni di sintassi.

Parametri

Nome Tipo Obbligatoria Descrizione
Poligono dynamic ✔️ Poligono o multipolygon nel formato GeoJSON.
level int Definisce il livello di cella richiesto. I valori supportati sono compresi nell'intervallo [0, 30]. Se non specificato, viene usato il valore predefinito 11.
Raggio real Raggio del buffer in metri. Se non specificato, viene usato il valore predefinito 0.

Restituisce

Matrice di stringhe di token di cella S2 che coprono un poligono o un multipolygon. Se il raggio è impostato su un valore positivo, la copertura sarà, oltre alla forma di input, di tutti i punti all'interno del raggio della geometria di input. Se il poligono, il livello, il raggio non è valido o il numero di celle supera il limite, la query genererà un risultato Null.

Nota

  • La copertura del poligono con token di cella S2 può essere utile nella corrispondenza delle coordinate ai poligoni che potrebbero includere queste coordinate e i poligoni corrispondenti ai poligoni.
  • Il poligono che copre i token è dello stesso livello di cella S2.
  • Il numero massimo di token per poligono è 65536.
  • Il datum geodetico usato per le misurazioni sulla Terra è una sfera. I bordi del poligono sono geodesici sulla sfera.
  • Se i bordi del poligono di input sono linee cartesiane dritte, è consigliabile usare geo_polygon_densify() per convertire i bordi planari in geodesici.

Motivazione per coprire i poligoni con token di cella S2

Senza questa funzione, è possibile adottare un approccio per classificare le coordinate in poligoni contenenti queste coordinate.

let Polygons = 
    datatable(description:string, polygon:dynamic)
    [  
      "New York",  dynamic({"type":"Polygon","coordinates":[[[-73.85009765625,40.85744791303121],[-74.16046142578125,40.84290487729676],[-74.190673828125,40.59935608796518],[-73.83087158203125,40.61812224225511],[-73.85009765625,40.85744791303121]]]}),
      "Seattle",   dynamic({"type":"Polygon","coordinates":[[[-122.200927734375,47.68573021131587],[-122.4591064453125,47.68573021131587],[-122.4755859375,47.468949677672484],[-122.17620849609374,47.47266286861342],[-122.200927734375,47.68573021131587]]]}),
      "Las Vegas", dynamic({"type":"Polygon","coordinates":[[[-114.9,36.36],[-115.4498291015625,36.33282808737917],[-115.4498291015625,35.84453450421662],[-114.949951171875,35.902399875143615],[-114.9,36.36]]]}),
    ];
let Coordinates = 
    datatable(longitude:real, latitude:real)
    [
      real(-73.95),  real(40.75), // New York
      real(-122.3),  real(47.6),  // Seattle
      real(-115.18), real(36.16)  // Las Vegas
    ];
Polygons | extend dummy=1
| join kind=inner (Coordinates | extend dummy=1) on dummy
| where geo_point_in_polygon(longitude, latitude, polygon)
| project longitude, latitude, description

Output

longitudine latitudine description
-73,95 40,75 Città di New York
-122.3 47.6 Seattle
-115.18 36.16 Las Vegas

Anche se questo metodo funziona in alcuni casi, è inefficiente. Questo metodo esegue un cross join, ovvero tenta di associare ogni poligono a ogni punto. Questo processo usa una grande quantità di memoria e risorse di calcolo. Si vuole invece associare ogni poligono a un punto con una probabilità elevata di successo di contenimento e filtrare altri punti.

Questa corrispondenza può essere ottenuta tramite il processo seguente:

  1. Conversione di poligoni in celle S2 di livello k,
  2. Conversione di punti nello stesso livello di celle S2 k,
  3. Unione su celle S2,
  4. Filtro in base a geo_point_in_polygon(). Questa fase può essere omessa se una certa quantità di falsi positivi è ok. L'errore massimo sarà l'area delle celle s2 al livello k oltre il limite del poligono.

Scelta del livello di cella S2

  • Idealmente vorremmo coprire ogni poligono con una o solo poche celle univoche in modo che nessun poligono condivide la stessa cella.
  • Se i poligoni sono vicini tra loro, scegliere il livello di cella S2 in modo che il bordo della cella sia più piccolo (4, 8, 12 volte più piccolo) rispetto al bordo del poligono medio.
  • Se i poligoni sono lontani l'uno dall'altro, scegliere il livello di cella S2 in modo che il bordo della cella sia simile o maggiore del bordo del poligono medio.
  • In pratica, coprire un poligono con più di 10.000 celle potrebbe non produrre buone prestazioni.
  • Casi d'uso di esempio:
  • Il livello di cella S2 5 potrebbe rivelarsi utile per coprire paesi/aree geografiche.
  • Il livello di cella S2 16 può coprire quartieri densi e relativamente piccoli di Manhattan (New York).
  • Il livello di cella S2 11 può essere utilizzato per coprire i sobborghi dell'Australia.
  • Il tempo di esecuzione delle query e il consumo di memoria possono variare notevolmente a causa di valori a livello di cella S2 diversi.

Avviso

La copertura di un poligono di grandi dimensioni con celle di piccole aree può portare a un'enorme quantità di celle di copertura. Di conseguenza, la query potrebbe restituire Null.

Nota

Suggerimenti per il miglioramento delle prestazioni:

  • Se possibile, ridurre le dimensioni della tabella prima del join, raggruppando le coordinate molto vicine tra loro usando il clustering geospaziale o filtrando le coordinate non disponibili a causa della natura dei dati o delle esigenze aziendali.
  • Se possibile, ridurre il numero di poligoni a causa della natura dei dati o delle esigenze aziendali. Filtrare i poligoni non necessari prima del join, definire l'ambito per l'area di interesse o unificare i poligoni.
  • In caso di poligoni molto grandi, ridurre le dimensioni usando geo_polygon_simplify().
  • La modifica del livello di cella S2 può migliorare le prestazioni e il consumo di memoria.
  • La modifica del tipo di join e dell'hint può migliorare le prestazioni e il consumo di memoria.
  • Se è impostato un raggio positivo, è possibile provare a migliorare le prestazioni ripristinando il raggio 0 sulla forma memorizzata nel buffer usando geo_polygon_buffer().

Esempio

L'esempio seguente classifica le coordinate in poligoni.

let Polygons = 
    datatable(description:string, polygon:dynamic)
    [
        'Greenwich Village', dynamic({"type":"Polygon","coordinates":[[[-73.991460000000131,40.731738000000206],[-73.992854491775518,40.730082566051351],[-73.996772,40.725432000000154],[-73.997634685522883,40.725786309886963],[-74.002855946639244,40.728346630056791],[-74.001413,40.731065000000207],[-73.996796995070824,40.73736378205173],[-73.991724524037934,40.735245208931886],[-73.990703782359589,40.734781896080477],[-73.991460000000131,40.731738000000206]]]}),
        'Upper West Side',   dynamic({"type":"Polygon","coordinates":[[[-73.958357552055688,40.800369095633819],[-73.98143901556422,40.768762584141953],[-73.981548752788598,40.7685590292784],[-73.981565335901905,40.768307084720796],[-73.981754418060945,40.768399727738668],[-73.982038573548124,40.768387823012056],[-73.982268248204349,40.768298621883247],[-73.982384797518051,40.768097213086911],[-73.982320919746599,40.767894461792181],[-73.982155532845766,40.767756204474757],[-73.98238873834039,40.767411004834273],[-73.993650353659021,40.772145571634361],[-73.99415893763998,40.772493009137818],[-73.993831082030937,40.772931787850908],[-73.993891252437052,40.772955194876722],[-73.993962585514595,40.772944653908901],[-73.99401262480508,40.772882846631894],[-73.994122058082397,40.77292405902601],[-73.994136652588594,40.772901870174394],[-73.994301342391154,40.772970028663913],[-73.994281535134448,40.77299380206933],[-73.994376552751078,40.77303955110149],[-73.994294029824005,40.773156243992048],[-73.995023275860802,40.773481196576356],[-73.99508939189289,40.773388475039134],[-73.995013963716758,40.773358035426909],[-73.995050284699261,40.773297153189958],[-73.996240651898916,40.773789791397689],[-73.996195837470992,40.773852356184044],[-73.996098807369748,40.773951805299085],[-73.996179459973888,40.773986954351571],[-73.996095245226442,40.774086186437756],[-73.995572265161172,40.773870731394297],[-73.994017424135961,40.77321375261053],[-73.993935876811335,40.773179512586211],[-73.993861942928888,40.773269531698837],[-73.993822393527211,40.773381758622882],[-73.993767019318497,40.773483981224835],[-73.993698463744295,40.773562141052594],[-73.993358326468751,40.773926888327956],[-73.992622663865575,40.774974056037109],[-73.992577842766124,40.774956016359418],[-73.992527743951555,40.775002110439829],[-73.992469745815342,40.775024159551755],[-73.992403837191887,40.775018140390664],[-73.99226708903538,40.775116033858794],[-73.99217809026365,40.775279293897171],[-73.992059084937338,40.775497598192516],[-73.992125372394938,40.775509075053385],[-73.992226867797001,40.775482211026116],[-73.992329346608813,40.775468900958522],[-73.992361756801131,40.775501899766638],[-73.992386042960277,40.775557180424634],[-73.992087684712729,40.775983970821372],[-73.990927174149746,40.777566878763238],[-73.99039616003671,40.777585065679204],[-73.989461267506471,40.778875124584417],[-73.989175778438053,40.779287524015778],[-73.988868617400072,40.779692922911607],[-73.988871874499793,40.779713738253008],[-73.989219022880576,40.779697895209402],[-73.98927785904425,40.779723439271038],[-73.989409054180143,40.779737706471963],[-73.989498614927044,40.779725044389757],[-73.989596493388234,40.779698146683387],[-73.989679812902509,40.779677568658038],[-73.989752702937935,40.779671244211556],[-73.989842247806507,40.779680752670664],[-73.990040102120489,40.779707677698219],[-73.990137977524839,40.779699769704784],[-73.99033584033225,40.779661794394983],[-73.990430598697046,40.779664973055503],[-73.990622199396725,40.779676064914298],[-73.990745069505479,40.779671328184051],[-73.990872114282197,40.779646007643876],[-73.990961672224358,40.779639683751753],[-73.991057472829539,40.779652352625774],[-73.991157429497036,40.779669775606465],[-73.991242817404469,40.779671367084504],[-73.991255318289745,40.779650782516491],[-73.991294887120119,40.779630209208889],[-73.991321967649895,40.779631796041372],[-73.991359455569423,40.779585883337383],[-73.991551059227476,40.779574821437407],[-73.99141982585985,40.779755280287233],[-73.988886144117032,40.779878898532999],[-73.988939656706265,40.779956178440393],[-73.988926103530844,40.780059292013632],[-73.988911680264692,40.780096037146606],[-73.988919261468567,40.780226094343945],[-73.988381050202634,40.780981074045783],[-73.988232413846987,40.781233144215555],[-73.988210420831663,40.781225482542055],[-73.988140000000143,40.781409000000224],[-73.988041288067166,40.781585961353777],[-73.98810029382463,40.781602878305286],[-73.988076449145055,40.781650935001608],[-73.988018059972219,40.781634188810422],[-73.987960792842145,40.781770987031535],[-73.985465811970457,40.785360700575431],[-73.986172704965611,40.786068452258647],[-73.986455862401996,40.785919219081421],[-73.987072345615601,40.785189638820121],[-73.98711901394276,40.785210319004058],[-73.986497781023601,40.785951202887254],[-73.986164628806279,40.786121882448327],[-73.986128422486075,40.786239001331111],[-73.986071135219746,40.786240706026611],[-73.986027274789123,40.786228964236727],[-73.986097637849426,40.78605822569795],[-73.985429321269592,40.785413942184597],[-73.985081137732209,40.785921935110366],[-73.985198833254501,40.785966552197777],[-73.985170502389906,40.78601333415817],[-73.985216218673656,40.786030501816427],[-73.98525509797993,40.785976205511588],[-73.98524273937646,40.785972572653328],[-73.98524962933017,40.785963139855845],[-73.985281779186749,40.785978620950075],[-73.985240032884533,40.786035858136792],[-73.985683885242182,40.786222123919686],[-73.985717529004575,40.786175994668795],[-73.985765660297687,40.786196274858618],[-73.985682871922691,40.786309786213067],[-73.985636270930442,40.786290150649279],[-73.985670722564691,40.786242911993817],[-73.98520511880038,40.786047669212785],[-73.985211035607492,40.786039554883686],[-73.985162639946992,40.786020999769754],[-73.985131636312062,40.786060297019972],[-73.985016964065125,40.78601423719563],[-73.984655078830457,40.786534741807841],[-73.985743787901043,40.786570082854738],[-73.98589227228328,40.786426529019593],[-73.985942854994988,40.786452847880334],[-73.985949561556794,40.78648711396653],[-73.985812373526713,40.786616865357047],[-73.985135209703174,40.78658761889551],[-73.984619428584324,40.786586016349787],[-73.981952458164173,40.790393724337193],[-73.972823037363767,40.803428052816756],[-73.971036786332192,40.805918478839672],[-73.966701,40.804169000000186],[-73.959647,40.801156000000113],[-73.958508540159471,40.800682279767472],[-73.95853274080838,40.800491362464697],[-73.958357552055688,40.800369095633819]]]}),
        'Upper East Side',   dynamic({"type":"Polygon","coordinates":[[[-73.943592454622546,40.782747908206574],[-73.943648235390199,40.782656161333449],[-73.943870759887162,40.781273026571704],[-73.94345932494096,40.780048275653243],[-73.943213862652243,40.779317588660199],[-73.943004239504688,40.779639495474292],[-73.942716005450905,40.779544169476175],[-73.942712374762181,40.779214856940001],[-73.942535563208608,40.779090956062532],[-73.942893408188027,40.778614093246276],[-73.942438481745029,40.777315235766039],[-73.942244919522594,40.777104088947254],[-73.942074188038887,40.776917846977142],[-73.942002667222781,40.776185317382648],[-73.942620205199006,40.775180871576474],[-73.94285645694552,40.774796600349191],[-73.94293043781397,40.774676268036011],[-73.945870899588215,40.771692257932997],[-73.946618690150586,40.77093339256956],[-73.948664164778933,40.768857624399587],[-73.950069793030679,40.767025088383498],[-73.954418260786071,40.762184104951245],[-73.95650786241211,40.760285256574043],[-73.958787773424007,40.758213471309809],[-73.973015157270069,40.764278692864671],[-73.955760332998182,40.787906554459667],[-73.944023,40.782960000000301],[-73.943592454622546,40.782747908206574]]]}),
    ];
let Coordinates = 
    datatable(longitude:real, latitude:real)
    [
        real(-73.9741), 40.7914, // Upper West Side
        real(-73.9950), 40.7340, // Greenwich Village
        real(-73.9584), 40.7688, // Upper East Side
    ];
let Level = 16;
Polygons
| extend covering = geo_polygon_to_s2cells(polygon, Level) // cover every polygon with s2 cell token array
| mv-expand covering to typeof(string)                     // expand cells array such that every row will have one cell mapped to its polygon
| join kind=inner hint.strategy=broadcast                  // assume that Polygons count is small (In some specific case)
(
    Coordinates
    | extend covering = geo_point_to_s2cell(longitude, latitude, Level) // cover point with cell
) on covering // join on the cell, this filters out rows of point and polygons where the point definitely does not belong to the polygon
| where geo_point_in_polygon(longitude, latitude, polygon) // final filtering for exact result
| project longitude, latitude, description

Output

longitudine latitudine description
-73.9741 40.7914 Lato superiore occidentale
-73.995 40.734 Greenwich Village
-73.9584 40.7688 Lato superiore orientale

Di seguito è riportato un miglioramento ancora maggiore sulla query precedente. Conteggio degli eventi storm per stato degli Stati Uniti. La query seguente esegue un join molto efficiente perché non contiene poligoni tramite join e usa l'operatore di ricerca

let Level = 6;
let polygons = materialize(
    US_States
    | project StateName = tostring(features.properties.NAME), polygon = features.geometry, id = new_guid());
let tmp = 
    polygons
    | project id, covering = geo_polygon_to_s2cells(polygon, Level) 
    | mv-expand covering to typeof(string)
    | join kind=inner hint.strategy=broadcast
            (
                StormEvents
                | project lng = BeginLon, lat = BeginLat
                | project lng, lat, covering = geo_point_to_s2cell(lng, lat, Level)
            ) on covering
    | project-away covering, covering1;
tmp | lookup polygons on id
| project-away id
| where geo_point_in_polygon(lng, lat, polygon)
| summarize StormEventsCountByState = count() by StateName

Output

StateName StormEventsCountByState
Florida 960
Georgia 1085
... ...

Nell'esempio seguente vengono filtrati i poligoni che non si intersecano con l'area del poligono di interesse. L'errore massimo è diagonale di lunghezza s2cell. Questo esempio si basa su un file raster poligonizzato di notte.

let intersection_level_hint = 7;
let area_of_interest = dynamic({"type": "Polygon","coordinates": [[[-73.94966125488281,40.79698248639272],[-73.95841598510742,40.800426144169315],[-73.98124694824219,40.76806170936614],[-73.97283554077148,40.7645513650551],[-73.94966125488281,40.79698248639272]]]});
let area_of_interest_covering = geo_polygon_to_s2cells(area_of_interest, intersection_level_hint);
EarthAtNight
| project value = features.properties.DN, polygon = features.geometry
| extend covering = geo_polygon_to_s2cells(polygon, intersection_level_hint)
| mv-apply c = covering to typeof(string) on
(
    summarize is_intersects = take_anyif(1, array_index_of(area_of_interest_covering, c) != -1)
)
| where is_intersects == 1
| count

Output

Conteggio
83

Numero di celle che saranno necessarie per coprire alcuni poligoni con celle S2 di livello 5.

let polygon = dynamic({"type":"Polygon","coordinates":[[[0,0],[0,50],[100,50],[0,0]]]});
print s2_cell_token_count = array_length(geo_polygon_to_s2cells(polygon, 5));

Output

s2_cell_token_count
286

La copertura di un poligono di grandi dimensioni con celle di piccole aree restituisce Null.

let polygon = dynamic({"type":"Polygon","coordinates":[[[0,0],[0,50],[100,50],[0,0]]]});
print geo_polygon_to_s2cells(polygon, 30);

Output

print_0

La copertura di un poligono di grandi dimensioni con celle di piccole aree restituisce Null.

let polygon = dynamic({"type":"Polygon","coordinates":[[[0,0],[0,50],[100,50],[0,0]]]});
print isnull(geo_polygon_to_s2cells(polygon, 30));

Output

print_0
1