Polygon Self-Intersecting when there is minimal wrapping at -180/180 failing

I am receiving a polygon-self intersecting error when I have a polygon that barely wraps across 180 to -180:

I have a simple mapping for a index setup as:

    index_mapping = {
        "time": {
            "type": "date",
            "format": "strict_date_optional_time||epoch_second"
        },
        "polygon": {
            "type": "geo_shape",
        }
    }

For this example I have a simple routine that generates a circle in python:

def generate_circle(lon, lat, radius, num_points):
    circle_points = []
    for i in range(num_points):
        theta = 2 * math.pi * i / num_points
        circle_lon = lon + radius * math.cos(theta)
        circle_lat = lat + radius * math.sin(theta)
        # Normalize longitude to be between -180 and 180
        circle_lon = (circle_lon + 180) % 360 - 180
        circle_points.append([circle_lon, circle_lat])
    # Repeat the first point to close the polygon
    circle_points.append(circle_points[0])
    return circle_points

The center point for my circle is:

        130.42233939828182 (lon)
        19.811126221598894 (lat)

In python I would generate it like this:

my_polygon = np.array( generate_circle( lon, lat, 50,360))

{
  "error": {
    "root_cause": [
      {
        "type": "mapper_parsing_exception",
        "reason": "failed to parse field [footprint] of type [geo_shape]"
      }
    ],
    "type": "mapper_parsing_exception",
    "reason": "failed to parse field [footprint] of type [geo_shape]",
    "caused_by": {
      "type": "illegal_argument_exception",
      "reason": "Polygon self-intersection at lat=26.53163948195544 lon=80.87758400007965"
    }
  },
  "status": 400
}

Here I made python picture of the circle properly rendered and added a dot at the self intersecting point, which is where ES "thinks" there is self intersecting that isn't actually happening based on the error:

I am sure there are other boundary cases, as I was drawing a lot of circles in a similar manner and getting these sorts of errors whenever I wrap either longitude/latitude rollover points.

Unfortunately you are right, there are some cases where the logic to handle polygons across the dateline does not work. I am pretty sure that is the same issue as described here:

For the time being, your best option is to not normalize the longitude. In that case, it should be handled properly.

@Ignacio_Vera thank you for the suggestion, that got me passed that specific issue.

def generate_circle(lon, lat, radius, num_points):
    circle_points = []
    for i in range(num_points):
        theta = 2 * math.pi * i / num_points
        circle_lon = lon + radius * math.cos(theta)
        circle_lat = lat + radius * math.sin(theta)
        # Normalize longitude to be between -180 and 180
        # Disabled normalization based on Ignacio comment
        # circle_lon = (circle_lon + 180) % 360 - 180
        circle_points.append([circle_lon, circle_lat])
    # Repeat the first point to close the polygon
    circle_points.append(circle_points[0])
    return circle_points

In some cases that normalization caused the outer ring to be CW not CCW. Since I didn't want to generate all my polygons again, I ended up using osgeo to MakeValid every polygon I send in.

from osgeo import ogr
# Other code ...
geom = ogr.CreateGeometryFromJson(json.dumps(polygon))
geom = geom.MakeValid()
fixed_poly = json.loads( geom.ExportToJson())

This ensured that my polygon orientation was always correct.

Thank you for the work around!

I spoke too soon, it seems that it isn't rendering correctly, even though I no longer normalized it.

I tried to manually force orientation with the geojson, but that did not work. I tried both clockwise and counterclockwise so it does seem like there is a real bug here. Not just a pure limitation.

This also breaks all the polygon intersection math.

Could you share how to generate that polygon again (center + radius + numPoints)? what are you using for rendering the polygon?

So the picture above is rendered using kibana 8.7.1 with ES 8.7.1
However what I realized as I was getting you the data, my hack to make things load properly using OGR, was actually breaking up the polygon. This was allowing me to post it, but OGR made a multipolygon out of it, and broke it at the intersect points where the polygon wrapped at 0 to 360.0deg. So I think Kibana was doing it's best to render it.

At the end of the day, I think what has to happen is the user in this case has to know if they are generating a polygon that wraps either 0/360 or -180/180 and normalize that properly? Seems a bit tedious, and I imagine if we cross two rollovers with a big rectangle or something it would break no matter what we did with normalization? Is this by design, or should this be written up as a bug or documented in the API in some way so the user knows they have to make sure the data they send in has a continuous range (e.g. no wrap)?

Just for completeness sake, here is the the polygon:

{"type":"Polygon","coordinates":[[[302.92086341761876,27.490763815308622],[302.79356042427986,27.925698812340162],[302.67298741602195,28.3622272781889],[302.5592374826112,28.800257420853267],[302.4524061372526,29.239696542290655],[302.35259140143626,29.680450999661442],[302.25989388920914,30.122426164537107],[302.17441689065095,30.56552638003878],[302.09626645430683,31.00965491587421],[302.02555146830287,31.45471392124137],[301.96238373983556,31.90060437557009],[301.90687807269455,32.34722603707438],[301.85915234243924,32.794477389091654],[301.81932756880803,33.24225558418813],[301.78752798489637,33.690456386014176],[301.76388110258836,34.13897410889808],[301.7485177736771,34.587701555172934],[301.7415722460496,35.036529950237544],[301.7431822142521,35.48534887536081],[301.75348886368545,35.9340461982476],[301.7726369076083,36.38250800139475],[301.80077461605094,36.830618508277354],[301.8380538356604,37.2782600074195],[301.88462999941066,37.725312774417816],[301.9406621250199,38.17165499200414],[302.00631280081996,38.6171626682521],[302.0817481577166,39.0617095530538],[302.1671378257763,39.505167053017],[302.2626548738555,39.947404144958924],[302.36847573057537,40.38828728820249],[302.48478008481874,40.82768033591292],[302.6117507638026,41.265444445747995],[302.7495735866485,41.70143799013421],[302.8984371912448,42.13551646652399],[303.0585328320616,42.56753240803472],[303.23005414645337,42.997335294921264],[303.4131968868537,43.42477146738829],[303.6081586161499,43.8496840403067],[303.81513836341173,44.271912820462234],[304.0343362370464,44.69129422703151],[304.2659529923705,45.107661216052115],[304.51018955051904,45.520843209729854],[304.76724646557443,45.9306660315057],[305.03732333678244,46.3369518478887],[305.3206181627496,46.73951911814781],[305.6173266345781,47.13818255304519],[305.9276413650129,47.53275308388409],[306.2517510508455,47.9230378432378],[306.58983956605664,48.308840158816594],[306.94208498349303,48.68995956202052],[307.308658523265,49.06619181281323],[307.68972342654047,49.43732894263203],[308.0854337539961,49.80315931712498],[308.49593310888486,50.16346772056881],[308.9213532854911,50.5180354638728],[309.36181284468324,50.86664051810873],[309.8174156193421,51.20905767552237],[310.28824915363913,51.54505873997459],[310.7743830814735,51.874412748725035],[311.27586745083516,52.19688622740585],[311.7927310024446,52.51224347993162],[312.3249794127151,52.82024691495128],[312.872593512867,53.120657410264535],[313.4355274978844,53.41323471639454],[314.0137071409002,53.69773790022862],[314.60702803050225,53.973925829306786],[315.2153538503149,54.241557696950736],[315.83851472199177,54.50039358798709],[316.4763056343856,54.75019508432434],[317.1284849830909,54.99072590910016],[317.79477324570837,55.22175260752558],[318.4748518189969,55.44304526192359],[319.168362044482,55.654378237798696],[319.87490444901977,55.855530957093094],[320.59403822620385,56.046288694095324],[321.32528098330226,56.22644338878443],[322.06810877657654,56.39579447173336],[322.8219564553403,56.55414969407513],[323.58621833195804,56.70132595547692],[324.36024919117125,56.837150122585776],[325.14336564771673,56.96145983002634],[325.9348478562294,57.07410425576355],[326.73394157198715,57.174944862506926],[327.53986055527764,57.26385609684183],[328.3517893061744,57.34072603793612],[329.1688861104635,57.40545698799663],[329.99028637152304,57.4579659971355],[330.81510619730443,57.498185315953194],[331.6424462063684,57.526062769940275],[332.47139551235574,57.541562050732026],[333.301035842475,57.544662920298265],[334.13044574269674,57.535361325294794],[334.9587048204492,57.51366942001212],[335.78489797479085,57.47961549760768],[336.6081195643078,57.433243830562645],[337.42747746434884,57.37461442253961],[338.24209696761335,57.303802674996746],[339.05112448546794,57.22089897301347],[339.8537310115708,57.12600819577525],[340.6491153142868,57.019249158031734],[341.4365068298216,56.90075398956338],[342.2151682338233,56.77066746026012],[342.9843976752123,56.629146258820825],[343.74353066203935,56.47635823332638],[344.4919415950779,56.312481602024235],[345.2290449504739,56.137704142597116],[345.95429611799796,55.95222236798478],[346.66719190614714,55.75624069649973],[347.3672707294709,55.54997062354386],[348.0541124969828,55.33362990171023],[348.72733822334624,55.107441735462686],[349.38660938668426,54.87163399594348],[350.03162705837855,54.62643846078696],[350.6621308311208,54.372090083127645],[351.2778975718147,54.10882629330557],[351.8787400257511,53.83688633609773],[352.46450529786017,53.55651064565967],[353.0350732358525,53.26794025975016],[353.59035473875804,52.97141627424447],[354.1302900128368,52.667179338423594],[354.6548467951196,52.35546919105925],[355.164018563012,52.036524236901485],[355.65782274649985,51.71058116281697],[356.13629895759414,51.37787459251926],[356.5995072497668,51.03863677857817],[357.0475264183048,50.69309733018851],[357.4804523507679,50.34148297501677],[357.89839643509475,49.98401735332456],[358.30148403137895,49.620920842484374],[358.6898530119405,49.252410409953264],[359.06365237305255,48.87869949275005],[359.42304092055053,48.49999790148514],[359.76818603054846,48.116511747018265],[0.09926248561315647,47.728443387862875],[0.41645138599152015,47.335991396513684],[0.719939134845953,46.939350542942904],[1.0099164959172424,46.53871179358918],[1.2865777215949663,46.13426232424782],[1.5501197490239493,45.72618554535866],[1.8007414616018766,45.31466113828049],[2.0386430130197284,44.899865101231946],[2.2640252108538177,44.4819698036713],[2.477088956629158,44.06114404797761],[2.6780347392298793,43.63755313738426],[2.8670621785275983,43.21135894920032],[3.0443696161258655,42.78272001243814],[3.2101537501737996,42.351791589042286],[3.364609311277276,41.91872575798955],[3.5079287766303326,41.48367150159911],[3.6403021195955456,41.046774793456876],[3.761916592079153,40.608178687419375],[3.8729565371701637,40.168023407218946],[3.973603229640616,39.726446436244224],[4.0640347420347895,39.283582607118475],[4.1444258342053475,38.839564190742685],[4.214947864284701,38.394520984511075],[4.275768719207116,37.94858039944368],[4.327052763021472,37.501867546014665],[4.368960801355294,37.0545053184858],[4.4016500605061974,36.606614477581985],[4.4252741797483885,36.158313731371024],[4.439983215547266,35.7097198142324],[4.445923656476,35.26094756381977],[4.443238447722538,34.81210999594014],[4.432067024165404,34.36331837728839],[4.412545351080264,33.91468229599077],[4.38480597161846,33.46630972992261],[4.348978060271737,33.018307112777336],[4.305187481606448,32.57077939787321],[4.253556853613783,32.12383011969274],[4.194205615082006,31.677561453156965],[4.127250096451111,31.23207427064339],[4.052803593661174,30.787468196761363],[3.9709764445517175,30.343841660903422],[3.8818761074130093,29.901291947595247],[3.785607241328904,29.45991524466936],[3.682271787987265,29.01980668929115],[3.5719690546670186,28.581060411867163],[3.454795798140929,28.143769577867563],[3.330846309260756,27.70802642759587],[3.200212498016184,27.27392231393972],[3.062983978882273,26.841547738137383],[2.9192481562896524,26.410992383594383],[2.7690903100712596,25.982345147785335],[2.6125936807554497,25.555694172275267],[2.4498395545904934,25.131126870895205],[2.2809073481991002,24.708729956105316],[2.1058746927732166,24.28858946357928],[1.9248175177302433,23.87079077504248],[1.737810133761072,23.455418639395596],[1.5449253152087388,23.042557192155193],[1.3462343817235216,22.63228997324116],[1.141807279146723,22.224699943140845],[0.9317126595807608,21.819869497478273],[0.7160179606073692,21.41788048001634],[0.49478948362044567,21.01881419411911],[0.26809247124196917,20.622751412700314],[0.03599118379310667,20.22977238668354],[359.79854897479385,19.839956851999098],[359.55582836546637,19.453384035141593],[359.30789111821815,19.070132657311788],[359.05479830908104,18.69028093716604],[358.7966103990843,18.31390659219582],[358.53338730453623,17.941086838759812],[358.2651884661934,17.571898390790473],[357.9920729172901,17.206417457197134],[357.7140993504047,16.844719737987305],[357.4313261831358,16.486880419128198],[357.14381162255995,16.13297416617006],[356.85161372844226,15.78307511665386],[356.55479047516843,15.437256871325266],[356.25339981236675,15.095592484178137],[355.94749972418515,14.758154451350693],[355.63714828718753,14.425014698897991],[355.32240372683225,14.096244569465842],[355.0033244724935,13.771914807890965],[354.6799692109839,13.452095545754087],[354.3523969385373,13.13685628491285],[354.02066701120606,12.826265880043135],[353.6848391936289,12.520392520217882],[353.344973706122,12.219303709554469],[353.00113127004596,11.923066246962119],[352.6533731513992,11.631746205023092],[352.3017612025897,11.345408908042131],[351.9463579023345,11.064118909300493],[351.58722639363606,10.787939967552349],[351.22443051978706,10.516935022802942],[350.8580348583518,10.25116617140931],[350.4881047530761,9.990694640546455],[350.1147063436757,9.735580762083007],[349.7379065934572,9.485883945912343],[349.35777331472343,9.241662652786882],[348.97437519192056,9.002974366704597],[348.5877818024832,8.769875566898692],[348.19806363533826,8.54242169948284],[347.80529210703,8.320667148805837],[347.4095395754323,8.104665208571308],[347.01087935101657,7.8944680527788265],[346.6093857056499,7.69012670654505],[346.2051338788986,7.491691016863793],[345.7982000818198,7.299209623365701],[345.388661498227,7.11272992913865],[344.9765962834209,6.932298071671432],[344.5620835603814,6.757958893983309],[344.14520341342353,6.589755916003247],[343.7260368793251,6.427731306262502],[343.30466593593957,6.27192585396463],[342.8811734883152,6.122378941497266],[342.45564335234565,5.979128517449432],[342.02816023598723,5.842211070197983],[341.59880971808064,5.711661602126401],[341.1676782248254,5.587513604538191],[340.73485300395964,5.469799033326062],[340.30042209670563,5.358548285457233],[339.8644743075486,5.253790176333521],[339.4270991719223,5.155551918083335],[338.98838692188156,5.063859098840911],[338.54842844985023,4.978735663066017],[338.1073152705358,4.900203892955208],[337.66513948111253,4.828284390993152],[337.2219937197763,4.762996063689982],[336.77797112278427,4.704356106547752],[336.333165280094,4.652379990296203],[335.8876701897246,4.607081448434575],[335.4415802109643,4.5684724661132465],[334.99499001655613,4.536563270385175],[334.54799454399426,4.511362321853771],[334.1006889460676,4.492876307739934],[333.65316854079134,4.481110136387182],[333.2055287608682,4.476066933220119],[332.75786510282234,4.477748038167188],[332.310273075951,4.486153004554844],[331.8628481512406,4.501279599476258],[331.4156857103913,4.523123805633477],[330.96888099509664,4.5516798246479535],[330.5225290567216,4.586940081830398],[330.07672470652307,4.628895232396827],[329.63156246655217,4.67753416911369],[329.18713652137876,4.732844031351325],[328.74354067077087,4.79481021552098],[328.30086828346396,4.863416386867021],[327.8592122521448,4.938644492582571],[327.4186649497765,5.020474776213274],[326.9793181873813,5.108885793310499],[326.54126317339626,5.203854428292638],[326.104590474709,5.305355912469711],[325.6693899794772,5.413363843184098],[325.23575086182734,5.527850204017621],[324.8037615485236,5.648785386012698],[324.37350968768993,5.776138209853326],[323.9450821196639,5.90987594894968],[323.51856485005095,6.049964353368259],[323.09404302504487,6.196367674548185],[322.6716009090692,6.349048690742885],[322.2513218647914,6.5079687331253],[321.83328833555254,6.6730877124938806],[321.4175818302481,6.844364146516068],[321.00428291069096,7.021755187445442],[320.59347118148,7.205216650248362],[320.1852252823901,7.394703041076108],[319.7796228832956,7.590167586018441],[319.37674068163193,7.791562260074912],[318.97665440239456,7.99883781628056],[318.57943880066875,8.211943814923726],[318.18516766667943,8.430828652793684],[317.7939138333446,8.655439592397645],[317.4057491863113,8.885722791086838],[317.02074467644957,9.121623330033348],[316.638970334775,9.363085242999949],[316.26049528976785,9.610051544847018],[315.88538778705197,9.862464259721621],[315.5137152113967,10.120264448876002],[315.14554411099783,10.383392238063367],[314.7809402239971,10.651786844461224],[314.4199685071922,10.925386603073862],[314.0626931668923,11.204128992567073],[313.7091776918697,11.487950660490066],[313.35948488835913,11.776787447841368],[313.0136769170546,12.070574412936457],[312.6718153320538,12.36924585453745],[312.3339611216998,12.672735334205933],[312.00017475126964,12.980975697842082],[311.67051620746105,13.293899096374675],[311.3450450446274,13.611437005567698],[311.0238204327128,13.93352024491118],[310.7069012068407,14.260078995564902],[310.39434591850943,14.591042817324878],[310.08621288835025,14.926340664583812],[309.7825602604047,15.265900901257742],[309.48344605787923,15.609651314652394],[309.18892824033776,15.957519128242927],[308.89906476229294,16.30943101334277],[308.61391363315965,16.66531309963663],[308.3335329785366,17.025090984554748],[308.05798110278124,17.38868974146482],[307.78731655284827,17.756033926659214],[307.5215981833594,18.12704758511551],[307.2608852228783,18.50165425500751],[307.0052373413621,18.879776970946015],[306.75471471876324,19.26133826592659],[306.5093781147583,19.64626017196306],[306.2692889395783,20.034464219384308],[306.03450932591716,20.425871434772937],[305.8051022018964,20.820402337522104],[305.58113136506074,21.217976934988904],[305.3626615573831,21.618514716220258],[305.14975854125277,22.021934644227176],[304.94248917642255,22.428155146783748],[304.7409214978869,22.837094105724887],[304.5451247946613,23.24866884471756],[304.35516968943097,23.66279611547823],[304.17112821903277,24.07939208241005],[303.99307391573046,24.49837230563037],[303.82108188923763,24.919651722360758],[303.6552289094384,25.343144626648524],[303.49559348974765,25.768764647389897],[303.3422559710459,26.196424724622762],[303.1952986061143,26.626037084056914],[303.054805644486,27.057513209809112],[302.92086341761876,27.490763815308625],[302.92086341761876,27.490763815308622]]]}

Here is the message it complains about

{
  "error": {
    "root_cause": [
      {
        "type": "mapper_parsing_exception",
        "reason": "failed to parse field [footprint] of type [geo_shape]"
      }
    ],
    "type": "mapper_parsing_exception",
    "reason": "failed to parse field [footprint] of type [geo_shape]",
    "caused_by": {
      "type": "illegal_argument_exception",
      "reason": "Polygon self-intersection at lat=19.897866172518697 lon=-53.64623045641713"
    }
  },
  "status": 400
}

Here is what it "should" look like

Here is how I am plotting it in python: This requires you have installed plotly

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import math
my_cir = # Copy what I had above (I have too many lines in this post) remove 1 set of [] from the json blob or fix my code below ;)
        
def generate_circle(lon, lat, radius, num_points):
    circle_points = []
    for i in range(num_points):
        theta = 2 * math.pi * i / num_points
        circle_lon = lon + radius * math.cos(theta)
        circle_lat = lat + radius * math.sin(theta)
        # Normalize longitude to be between -180 and 180
        circle_lon = (circle_lon + 180) % 360 - 180
        circle_points.append([circle_lat, circle_lon])
    # Repeat the first point to close the polygon
    circle_points.append(circle_points[0])
    return np.array(circle_points)

def add_trace( data, color, fig, fill=False):
    lat = data[:,1]
    lon = data[:,0]
    print(lat.shape)
    print(lon.shape)
    if not fill:
        fig.add_trace(go.Scattergeo(lat=lat,
            lon=lon,mode='lines', line=dict(color=color, width=1 ),
            showlegend=False))
    else:
        fig.add_trace(go.Scattergeo(lat=np.flip(lat),
            lon=np.flip(lon),mode='lines', line=dict(color=color, width=0.5 ),
            fill = 'toself',
            fillcolor=color,
            showlegend=False))

fig = go.Figure()

lat=19.897866172518697
lon=-53.64623045641713

fig.add_trace(go.Scattergeo(lat=[lat], lon=[lon], mode='markers',
    showlegend=False, marker=dict(color="red", size=3)))

add_trace(np.array(my_cir["coordinates"]), "green", fig)
fig.update_layout(
    showlegend=True,
    geo=dict(
        resolution=50,
        showland=True,
        landcolor='rgb(217, 217, 217)',
        countrycolor='rgb(204, 204, 204)',
        showcountries=True,
        showcoastlines=True,
        coastlinewidth=1,
        showocean=True,
        oceancolor='rgb(230, 230, 255)',
        projection_type='natural earth'
    )
)
fig.write_image( "mycirc.png", format="png", scale=4 )

I will have a closer look tomorrow but there is something very odd in the polygon you shared. The first point looks like [302.92086341761876,27.490763815308622] but further in the list I see the point [0.09926248561315647,47.728443387862875]. It seems it is considering the longitude ranges between 0 and 360. I would expect that to confuse Elasticsearch.

@Ignacio_Vera I will get you a better example later today, it turns out I did this in haste and had my normalization wrong, and I don't want you to waste your time with that one (looks like the conditioning that Scattergeo does on lat/lon values fix my oversight).

So I wanted to generate something more inline with what I am doing.
So all of my circles I am sending to ES are generated in X,Y,Z using a unit sphere.

To demonstrate this I wrote a python script for you that you can use / modify with elasticsearch 8.7. It handles the mappings and such so other than telling Kibana the index exists and time is what you are using, it should just work for you.

It will dump out to stdout the 2 polygons, the first loads fine but plots are wrong:
Here is the one that is considered valid, but plots wrong:

I normalize so that my coordinate system is 0-360deg for longitude (however the geo_point I was getting errors when I had it from 0-360, so I left it at -180,180).

I noticed in some cases my winding could be wrong, so I enforce it using geojson_rewind.

Here is the failing polygon:

{"time": "2023-05-23T00:05:00", "center_point": [1.25, 45.0], "poly": {"type": "Polygon", "coordinates": [[[0.866430833706147, 34.69353612407307], [0.5596083542616839, 34.69658776328635], [0.25284221113031435, 34.701383745964414], [359.9461574779588, 34.707924804887966], [359.6395792669232, 34.71621193919848], [359.33313274064017, 34.726246414299965], [359.026843124123, 34.73802976173303], [358.7207357167928, 34.75156377901997], [358.4148359045565, 34.76685052947927], [358.1091691719616, 34.783892342008244], [357.8037611144385, 34.80269181083146], [357.4986374506427, 34.82325179521299], [357.19382403490624, 34.84557541913001], [356.88934686981094, 34.86966607090475], [356.58523211889434, 34.895527402792204], [356.2815061195004, 34.92316333051972], [355.9781953957858, 34.95257803277539], [355.6753266718949, 34.98377595064078], [355.3729268853147, 35.01676178696428], [355.0710232004231, 35.051540505669934], [354.7696430222418, 35.08811733099728], [354.46881401040804, 35.12649774666669], [354.1685640933778, 35.16668749496457], [353.86892148287376, 35.20869257574249], [353.5699146885917, 35.25251924532366], [353.2715725331805, 35.29817401531018], [352.97392416750847, 35.34566365128352], [352.6769990862319, 35.39499517139072], [352.380827143681, 35.44617584480801], [352.0854385700784, 35.49921319007342], [351.7908639881056, 35.554114973278956], [351.4971344298352, 35.610889206112816], [351.20428135404427, 35.66954414374149], [350.9123366639272, 35.730088282520796], [350.62133272522436, 35.7925303575246], [350.33130238478714, 35.856879339879136], [350.04227898959516, 35.92314443389028], [349.75429640624776, 35.991335073950594], [349.46738904094735, 36.06146092121179], [349.18159185999696, 36.133531860008155], [348.8969404108311, 36.20755799401527], [348.6134708436035, 36.28354964212769], [348.3312199333522, 36.36151733403827], [348.05022510276626, 36.44147180550133], [347.7705244455767, 36.52342399326055], [347.4921567505969, 36.60738502962158], [347.2151615264363, 36.69336623664862], [346.939579026914, 36.781379119962956], [346.6654502771979, 36.871435362120344], [346.3928171006979, 36.963546815543204], [346.12172214673876, 37.057725494982265], [345.8522089190438, 37.153983569481014], [345.58432180505736, 37.252333353815324], [345.31810610613655, 37.352787299378925], [345.05360806864365, 37.455357984484415], [344.79087491597085, 37.5600581040477], [344.5299548815295, 37.666900458622564], [344.2708972427383, 37.775897942750404], [344.0137523560431, 37.88706353258851], [343.7585716930048, 38.00041027277884], [343.5054078774897, 38.115951262517285], [343.2543147239994, 38.23369964078163], [343.00534727717724, 38.35366857067507], [342.7585618525281, 38.475871222839466], [342.514016078391, 38.600320757891154], [342.27176893920216, 38.72703030783011], [342.0318808200881, 38.85601295637062], [341.79441355282836, 38.98728171814032], [341.55943046322733, 39.12084951669152], [341.32699641993474, 39.256729161266854], [341.0971778847556, 39.39493332225912], [340.87004296448794, 39.53547450530229], [340.64566146432867, 39.678365023928876], [340.4241049428856, 39.82361697072602], [340.20544676883395, 39.97124218692018], [339.9897621792542, 40.121252230318056], [339.77712833968627, 40.273658341528346], [339.5676244059345, 40.4284714083869], [339.3613315876552, 40.585701928504854], [339.158333213755, 40.74535996985683], [338.958714799628, 40.90745512932376], [338.762564116253, 41.071996489102226], [338.56997126117017, 41.238992570889906], [338.3810287313517, 41.408451287753714], [338.19583149797376, 41.58037989358539], [338.0144770830929, 41.75478493004663], [337.83706563822193, 41.93167217090373], [337.66370002479124, 42.11104656364932], [337.4944858964743, 42.29291216830745], [337.329531783343, 42.477272093315946], [337.16894917780894, 42.664128428378724], [337.0128526222914, 42.85348217417944], [336.86135979853907, 43.04533316884698], [336.7145916185146, 43.239680011062404], [336.57267231673285, 43.43651997969731], [336.4357295439221, 43.63584894987318], [336.30389446185393, 43.83766130533247], [336.17730183916154, 44.04194984701362], [336.0560901479361, 44.248705697723786], [335.94040166086006, 44.45791820280614], [335.8303825485979, 44.669574826702245], [335.7261829771278, 44.88366104531423], [335.62795720465147, 45.100160234077165], [335.53586367767286, 45.31905355165888], [335.45006512578124, 45.54031981921202], [335.37072865461784, 45.76393539511291], [335.2980258364384, 45.989874045132346], [335.23213279761575, 46.21810680799637], [335.17323030234763, 46.44860185630898], [335.1215038317524, 46.68132435282627], [335.07714365744175, 46.916236302089125], [335.0403449085608, 47.153296397443796], [335.01130763117845, 47.39245986350343], [334.99023683879017, 47.6336782941302], [334.97734255257296, 47.87689948604853], [334.97283982989194, 48.12206726823311], [334.9769487794127, 48.36912132725297], [334.9898945610161, 48.61799702879463], [335.011907368544, 48.86862523563314], [335.0432223932244, 49.12093212237054], [335.0840797654378, 49.374838987317716], [335.13472447228315, 49.63026206195575], [335.1954062481955, 49.88711231848178], [335.2663794356464, 50.14529527601582], [335.347902812733, 50.40471080612654], [335.4402393842288, 50.66525293841942], [335.543656132431, 50.92680966702595], [335.6584237239035, 51.18926275893233], [335.78481616797376, 51.45248756519687], [335.9231104226133, 51.71635283622071], [336.07358594311034, 51.980720542362285], [336.23652416873557, 52.24544570131778], [336.4122079424227, 52.51037621383077], [336.6009208583326, 52.775352709441144], [336.8029465320571, 53.04020840413812], [337.01856778815886, 53.30476897194236], [337.24806575974145, 53.568852432607066], [337.4917188948207, 53.83226905779681], [337.749801864431, 54.09482129827268], [338.0225843676725, 54.35630373478254], [338.3103298292931, 54.61650305552088], [338.6132939859373, 54.87519806318313], [338.93172335787904, 55.132159714788735], [339.26585360393307, 55.3871511975818], [339.6159077582998, 55.63992804443332], [339.9820943493884, 55.890238292257926], [340.3646054021727, 56.13782268701533], [340.76361432739986, 56.38241493888449], [341.17927370298463, 56.62374203116916], [341.6117129552027, 56.86152458640977], [342.0610359498299, 57.09547729302814], [342.52731850615976, 57.32530939561137], [343.01060584984344, 57.55072525163998], [343.51091002370674, 57.77142495707429], [344.0282072790595, 57.98710504272547], [344.562435473469, 58.19745924274699], [345.1134915044421, 58.402179335882956], [345.68122881186315, 58.600956059301375], [346.2654549852644, 58.793480093921126], [346.8659295149297, 58.979443119116006], [347.4823617283381, 59.15853893355518], [348.11440895538027, 59.330464637729094], [348.7616749669951, 59.49492187242768], [349.42370873222444, 59.65161810610933], [350.1000035380352, 59.80026796274674], [350.78999651448953, 59.940594580393835], [351.493068604853, 60.07233098942182], [352.2085450159587, 60.19522149815874], [352.93569617856235, 60.30902307258184], [353.67373924055573, 60.41350669579386], [354.42184010783205, 60.50845869230694], [355.1791160384488, 60.593682001701694], [355.9446387857097, 60.66899738605511], [356.7174382751335, 60.734244555672085], [357.4965067892865, 60.78928319812665], [358.28080362347566, 60.83399389643268], [359.0692601646763, 60.868278923316325], [359.8607853361847, 60.89206290003913], [0.6542713416947095, 60.90529330999572], [1.4485996351365316, 60.90794085934353], [2.2426470369687195, 60.89999967916492], [3.035291913909248, 60.88148736605587], [3.8254203374747817, 60.852444860516286], [4.611932137239023, 60.81293616501333], [5.393746767394305, 60.76304790603597], [6.169808909900837, 60.702888746783906], [6.93909374404501, 60.63258865928047], [7.700611820341976, 60.55229806660743], [8.453413486100885, 60.46218686759066], [9.196592820269075, 60.36244335758248], [9.929291046018818, 60.25327305997009], [10.65069940057208, 60.13489748368275], [11.360061452616947, 60.0075528222731], [12.056674868046798, 59.871488610130385], [12.73989263436357, 59.72696635106285], [13.409123762720299, 59.57425813390103], [14.063833494060873, 59.41364524895557], [14.70354304204676, 59.24541681815591], [15.327828910387893, 59.069868450541854], [15.936321825820698, 58.88730093351997], [16.528705330344224, 58.69801896897295], [17.10471407752226, 58.5023299619588], [17.664131877789714, 58.30054286839245], [18.20678953690225, 58.0929671067947], [18.73256253008043, 57.87991153794411], [19.24136855216244, 57.66168351509879], [19.733164981351706, 57.438588006379135], [20.20794629104779, 57.210926789929644], [20.665741440924194, 56.97899772161352], [21.106611274962347, 56.743094074240254], [21.53064595067883, 56.50350394668202], [21.937962420364443, 56.26050974069605], [22.328701981862935, 56.014387702831755], [22.70302791330039, 55.76540752845633], [23.06112320327503, 55.51383202467274], [23.403188385355747, 55.259916828720755], [23.729439483329543, 55.00391017833713], [24.040106071492232, 54.74605273049482], [24.335429452388382, 54.48657742493703], [24.61566095276919, 54.22570938896005], [24.88106033714189, 53.963665879972694], [25.131894337107553, 53.70065626246324], [25.36843529372146, 53.43688201613001], [25.59095990932815, 53.17253677207467], [25.7997481047164, 52.907806374112326], [25.995081976976792, 52.64286896241628], [26.177244853118395, 52.377895076883476], [26.346520434283946, 52.113047777777204], [26.503192025286353, 51.848482781372795], [26.64754184415318, 51.58434860849965], [26.779850406398623, 51.32078674403526], [26.900395978829124, 51.057931805564586], [27.009454097824687, 50.795911719569006], [27.10729714720202, 50.53484790365299], [27.19419399096455, 50.27485545345345], [27.270409656453978, 50.01604333300476], [27.336205063648322, 49.75851456745388], [27.39183679658379, 49.502366437132586], [27.4375569131181, 49.24769067209921], [27.47361278948864, 48.99457364635976], [27.500246996356736, 48.74309657106853], [27.517697203256375, 48.49333568609204], [27.526196108590966, 48.245362449396], [27.525971392533506, 47.99924372378587], [27.51724569039277, 47.75504196059538], [27.500236584200707, 47.5128153799761], [27.475156610461795, 47.272618147494555], [27.44221328217793, 47.03450054679121], [27.401609123425374, 46.79850914810017], [27.353541714912467, 46.56468697246725], [27.298203749088202, 46.33307365153996], [27.235783093504324, 46.10370558283471], [27.166462861254672, 45.876616080415204], [27.090421487429126, 45.65183552094125], [27.007832810623142, 45.42939148507016], [26.9188661586399, 45.20930889421239], [26.823686437609467, 44.991610142661486], [26.72245422383122, 44.776315225133004], [26.61532585771755, 44.5634418597617], [26.50245353928642, 44.35300560661655], [26.38398542471009, 44.145019981804936], [26.260065723483876, 43.93949656724441], [26.130834795829855, 43.73644511618897], [25.996429249996027, 43.535873654601936], [25.85698203915365, 43.3377885784733], [25.71262255763253, 43.14219474718276], [25.56347673626965, 42.9490955730132], [25.409667136675523, 42.75849310692198], [25.251313044251503, 42.5703881206783], [25.088530559816377, 42.38478018547676], [24.921432689721826, 42.201667747137314], [24.750129434357518, 42.02104819800206], [24.5747278749638, 41.84291794563849], [24.39533225868672, 41.667272478458734], [24.212044081823024, 41.494106428362464], [24.02496217121717, 41.32341363051041], [23.834182763782053, 41.155187180333144], [23.639799584126308, 40.98941948787874], [23.44190392027849, 40.826102329599905], [23.24058469750804, 40.66522689768007], [23.035928550247036, 40.50678384699479], [22.828019892125553, 40.35076333980291], [22.616940984135795, 40.197155088259265], [22.40277200094721, 40.04594839483836], [22.185591095395637, 39.89713219075565], [21.965474461175802, 39.75069507247072], [21.742496393766828, 39.606625336353964], [21.51672934962403, 39.46491101159567], [21.288244003671252, 39.325539891433955], [21.057109305130894, 39.18849956277555], [20.82339253172745, 39.05377743428037], [20.5871593423052, 38.92136076297917], [20.348473827896328, 38.791236679489984], [20.107398561281798, 38.663392211897786], [19.863994645082187, 38.5378143083586], [19.618321758419995, 38.41448985848717], [19.37043820219276, 38.293405713585344], [19.120400942996184, 38.17454870576548], [18.868265655736877, 38.057905666021774], [18.614086764974104, 37.94346344129962], [18.357917485027997, 37.83120891061141], [18.09980985889291, 37.72112900024533], [17.839814795993277, 37.61321069811131], [17.57798210881782, 37.50744106726701], [17.314360548469494, 37.40380725866434], [17.048997839164713, 37.30229652315579], [16.781940711718107, 37.202896222797875], [16.513234936045308, 37.10559384148726], [16.242925352717577, 37.01037699496405], [15.971055903599733, 36.91723344021456], [15.697669661603754, 36.82615108430514], [15.422808859587747, 36.73711799267645], [15.14651491843091, 36.650122396927145], [14.868828474312977, 36.565152702113615], [14.589789405226668, 36.48219749359218], [14.30943685675112, 36.40124554342813], [14.027809267111877, 36.32228581639537], [13.744944391555293, 36.24530747558918], [13.460879326060535, 36.170299887673345], [13.175650530415794, 36.09725262778226], [12.8892938506811, 36.02615548409733], [12.601844541062178, 35.95699846211633], [12.313337285216676, 35.889771788632935], [12.023806217015874, 35.82446591544391], [11.733284940782596, 35.76107152279909], [11.441806551025934, 35.69957952260989], [11.149403651693433, 35.63998106143039], [10.85610837495949, 35.58226752322499], [10.561952399570146, 35.52643053193531], [10.266966968761551, 35.47246195385881], [9.97118290777081, 35.4203538998509], [9.67463064095665, 35.370098727361494], [9.377340208546798, 35.32168904231643], [9.079341283028441, 35.27511770085406], [8.78066318519825, 35.230377810925944], [8.481334899888054, 35.18746273377091], [8.181385091380037, 35.14636608527069], [7.880842118528506, 35.10708173719517], [7.579734049600802, 35.06960381834463], [7.278088676853258, 35.03392671559611], [6.975933530854775, 35.00004507486043], [6.673295894572561, 34.96795380195624], [6.370202817233007, 34.93764806340674], [6.066681127970753, 34.909123287164725], [5.7627574492784674, 34.88237516327083], [5.458458210270408, 34.85739964444999], [5.153809659771468, 34.834192946650404], [4.8488378792444, 34.81275154952897], [4.543568795566443, 34.7930721968873], [4.23802819366756, 34.77515189706143], [3.9322417290413796, 34.75898792326871], [3.6262349401405913, 34.74457781391457], [3.3200332606678558, 34.73191937286184], [3.0136620317728102, 34.72101066966497], [2.707146514167448, 34.711850039771285], [2.400511900168965, 34.704436084690975], [2.0937833256823524, 34.698767672137286], [1.7869858821323987, 34.694843936138554], [1.4801446283568112, 34.69266427712282], [1.1732846024694936, 34.69222836197581], [0.866430833706147, 34.69353612407307]]]}}

import math
import json
import socket
import numpy as np
from datetime import datetime, timedelta
from elasticsearch import Elasticsearch, helpers
from elasticsearch.helpers import BulkIndexError
from geojson_rewind import rewind


def lla2ecef(lat, lon, alt):
    # WGS-84 ellipsoid parameters
    a = 6378137  # semi-major axis (equatorial radius) in meters
    f = 1 / 298.257223563  # flattening
    b = a * (1 - f)  # semi-minor axis (polar radius) in meters

    # convert lat, lon to radians
    lat_rad = math.radians(lat)
    lon_rad = math.radians(lon)

    # Prime vertical radius of curvature at the specific latitude
    N = a / math.sqrt(1 - ((2*f - f**2) * math.sin(lat_rad)**2))

    # Calculate ECEF coordinates
    x = (N + alt) * math.cos(lat_rad) * math.cos(lon_rad)
    y = (N + alt) * math.cos(lat_rad) * math.sin(lon_rad)
    z = (b**2/a**2 * N + alt) * math.sin(lat_rad)

    return x, y, z

def ecef2lla(x: float, y: float, z: float) -> tuple[float, float, float]:
    """
    Convert ECEF coordinates to LLA coordinates.

    Args:
        x: X coordinate in meters.
        y: Y coordinate in meters.
        z: Z coordinate in meters.

    Returns:
        Latitude in degrees.
        Longitude in degrees.
        Altitude in meters.
    """

    # Earth constants
    a = 6378137  # Semi-major axis of the Earth in meters.
    e = 8.181919084261345e-2  # Eccentricity of the Earth.

    # Calculate the radius vector.
    r = np.sqrt(x ** 2 + y ** 2)

    # Calculate the eccentricity squared.
    e_sq = e ** 2

    # Calculate the flattening of the Earth.
    f = 1 / 298.257223563

    # Calculate the semi-minor axis of the Earth in meters.
    b = a * (1 - f)

    # Calculate the eccentricity squared of the prime vertical.
    ep_sq = (a ** 2 - b ** 2) / b ** 2

    # Calculate the second eccentricity squared.
    ee = a ** 2 - b ** 2

    # Calculate the auxiliary quantity f.
    f = 54 * b ** 2 * z ** 2

    # Calculate the auxiliary quantity g.
    g = r ** 2 + (1 - e_sq) * z ** 2 - e_sq * ee

    # Calculate the auxiliary quantity c.
    c = e_sq ** 2 * f * r ** 2 / g ** 3

    # Calculate the auxiliary quantity s.
    s = (1 + c + np.sqrt(c ** 2 + 2 * c)) ** (1 / 3.)

    # Calculate the auxiliary quantity p.
    p = f / (3. * (g ** 2) * (s + (1. / s) + 1) ** 2)

    # Calculate the auxiliary quantity q.
    q = np.sqrt(1 + 2 * p * e_sq ** 2)

    # Calculate the reduced semi-major axis.
    r_0 = -(p * e_sq * r) / (1 + q) + np.sqrt(0.5 * (a ** 2) * (1 + (1. / q))
                                              - p * (z ** 2) * (1 - e_sq) / (q * (1 + q)) - 0.5 * p * (r ** 2))

    # Calculate the distance to the prime vertical.
    u = np.sqrt((r - e_sq * r_0) ** 2 + z ** 2)

    # Calculate the distance to the ellipsoid.
    v = np.sqrt((r - e_sq * r_0) ** 2 + (1 - e_sq) * z ** 2)

    # Calculate the height above the ellipsoid.
    z_0 = (b ** 2) * z / (a * v)

    # Calculate the altitude.
    h = u * (1 - b ** 2 / (a * v))

    # Calculate the latitude.
    phi = np.arctan((z + ep_sq * z_0) / r)

    # Calculate the longitude.
    lambd = np.arctan2(y, x)

    # Return the latitude, longitude, and altitude.
    return phi * 180 / np.pi, lambd * 180 / np.pi, h


def create_index_with_mapping(es_client, index_name):
    """
    Create Elasticsearch index with the given mapping.

    :param es: Elasticsearch instance
    :param index_name: Name of the index to create
    """

    index_mapping = {
        "time": {
            "type": "date",
            "format": "strict_date_optional_time||epoch_second"
        },
        "center_point": {
            "type": "geo_point"
        },
        "poly": {
            "type": "geo_shape",
        }
    }
    es_client.options(ignore_status=[400]).indices.create(index=index_name)
    es_client.indices.put_mapping(index=index_name, properties=index_mapping)

# Function to generate a circle's points in ECEF and convert to LLA
def generate_circle_ecef_to_lla(x_center, y_center, z_center, radius, num_points):
    circle_points = []
    for i in range(num_points):
        theta = 2 * math.pi * i / num_points
        x = x_center + radius * math.cos(theta)
        y = y_center + radius * math.sin(theta)
        z = z_center
        lat, lon, _ = ecef2lla(x, y, z)
        # Map between 0 and 360.0
        lon = np.mod( lon + 360.0, 360.0 )
        circle_points.append([lon, lat])  # Adjusted for [lon, lat] order
    # Repeat the first point to close the polygon
    circle_points.append(circle_points[0])
    return circle_points


def yield_circles(radius, lat, sides):
    #for ii in range(121, 124):
    for ii in range(2):
        circle_time = start_time + timedelta(minutes=5*ii)

        #  288 circles in 1 day
        lon = ii*1.25
        x, y, z = lla2ecef(lat, lon, 0.0)

        points = generate_circle_ecef_to_lla(x, y, z, radius, sides)

        circle = {}
        circle["time"] = circle_time.isoformat()
        # This has to be between (-180,180)
        circle["center_point"] = ((lon+180.0) % 360.0 - 180.0, lat)
        circle["poly"] = {
            'type': 'Polygon',
            'coordinates': [points]}
        inputval  = {"geometry": circle["poly"], "type": "Feature"}
        poly_rw = rewind(inputval)
        circle["poly"] = poly_rw["geometry"]
        print(json.dumps(circle))
        yield circle


# Hard coded, but change as your heart desires
radius = 2000e3
sides = 360
lat = 45.0

index_name = "circles"
start_time = datetime.combine(datetime.today().date(), datetime.min.time())

es_host = f"{socket.gethostname()}"
es_port = 9200
es_client = Elasticsearch(f"http://{es_host}:{es_port}", request_timeout=30.0)

create_index_with_mapping(es_client, index_name)

data = ({
    "_index": index_name,
    "_source": es_record,
} for es_record in yield_circles(radius, lat, sides))

try:
    success, errors = helpers.bulk(es_client, data, raise_on_error=False)
    # success, errors =  helpers.bulk(es_client, data, raise_on_error=False)
except BulkIndexError as e:
    print(f"{e}")
print(json.dumps(errors))

I am not sure if I follow what you are doing but the final polygon seems wrong. If you get the maximum longitude and the minimum longitude of the polygon, I would expect that maxLon - minLon < 180 if it is not normalised.

maybe I don't understand what you mean by not normalized. I am sending in geodesic coordinates that are in the range of [0-360deg lat], originally the longitude was [-180,180].

Can you explain why you would expect valid polygons to be less than 180.0?

This is how I am thinking about it:
If I have a polygon that spans -150 to 150 in longitude that would give me 150 - (-150) = 300 (say you were drawing a large box).

or if you drew a box from 10 to 200 in longitude, that would still be valid and would be 190.

Elasticsearch only considers polygons across the dateline if maxLon - minLon > 180. In that case, depending on the orientation (which currently sometimes is computed wrongly) might decide to that the polygon crosses the dateline or not:

  • if the polygon orientation is correct, then the polygon does not cross the dateline and maxLon - minLon > 180. For example it goes from -150 to 150

  • if the polygon orientation is correct, then the polygon does cross the dateline. In your example, the longitudes will go from 150 to 210 (360 - 210) and maxLon - minLon < 180

So your polygon example where min longitudes are around 0 and max longitudes are around 360, it makes no sense on what we are expecting,

So at the end of the day, I need either normalize between -180,180 or 0-360 depending on if my polygon wraps (e.g. maxLon - minLon < 180.0).

When I modify my code I provided earlier, that resolves this issue.

Do you know if they are planning to fix the issue with not always computing the orientation correctly?

yes, we are planing to fix it.