From ac0d14758dbfb6141921d6d44577a23664193d47 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Fri, 19 Jun 2026 16:24:22 +0700 Subject: [PATCH 1/7] feat: Custom viewshed polygon reconstructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At higher angle counts a traditional polygon unioning of all the polar segments suffers from O(N²). For example, even in Rust a 3600 angled viewshed takes ~20s to create its GeoJSON. And many minutes in browser JS. Therefore we have to design an entirely new algorithm that takes advantage of the fact that we are only ever adding new pieces to the growing viewshed in a very specific way. Namely we only ever add polar segments, that are always contained within the arc of a single angle, onto the _side_ of a polygon. This returns polygon reconstruction to a ~200ms for 3600 angled viewsheds. --- .github/workflows/ci.yaml | 4 +- benchmarks/cardiff-viewshed.json | 2 +- benchmarks/run.sh | 2 +- crates/total-viewsheds/src/main.rs | 6 +- crates/total-viewsheds/src/output/ascii.rs | 128 ++- .../total-viewsheds/src/output/bresenham.rs | 2 +- .../src/output/viewsheds/growable_polygon.rs | 431 ++++++++++ .../src/output/viewsheds/join.rs | 32 - .../src/output/viewsheds/joiner_common.rs | 763 ++++++++++++++++++ .../src/output/viewsheds/joiner_final.rs | 476 +++++++++++ ...{visible_polygon.rs => segment_polygon.rs} | 110 +-- .../src/output/viewsheds/viewshed.rs | 32 +- .../total-viewsheds/src/storage/segments.rs | 2 +- 13 files changed, 1868 insertions(+), 122 deletions(-) create mode 100644 crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs delete mode 100644 crates/total-viewsheds/src/output/viewsheds/join.rs create mode 100644 crates/total-viewsheds/src/output/viewsheds/joiner_common.rs create mode 100644 crates/total-viewsheds/src/output/viewsheds/joiner_final.rs rename crates/total-viewsheds/src/output/viewsheds/{visible_polygon.rs => segment_polygon.rs} (72%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8a5b136..adc09b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,10 +38,12 @@ jobs: sudo apt-get update -y sudo apt-get install libgdal-dev gdal-bin - uses: Swatinem/rust-cache@v2 + - name: Install cargo-nextest + uses: taiki-e/install-action@nextest - name: Build run: cargo build --verbose --all - name: Run tests - run: cargo test + run: cargo nextest run e2e: name: End to end tests diff --git a/benchmarks/cardiff-viewshed.json b/benchmarks/cardiff-viewshed.json index e4e80c9..f17c133 100644 --- a/benchmarks/cardiff-viewshed.json +++ b/benchmarks/cardiff-viewshed.json @@ -1 +1 @@ -{"type":"MultiPolygon","coordinates":[[[[-3.30941915032233,51.53780568725623],[-3.3080861950590075,51.537463838363806],[-3.3067278272108385,51.539473525977904],[-3.308051058272776,51.53982984855978],[-3.30941915032233,51.53780568725623]]],[[[-3.3067532597671656,51.5371219743089],[-3.3054203448869783,51.536780095091295],[-3.3040817012645824,51.53876043015245],[-3.3027585322282165,51.53840406658359],[-3.301374398918109,51.5403555525948],[-3.3026874384036735,51.54072628157445],[-3.3040816466043905,51.538760511153754],[-3.3040814266495504,51.53876083653242],[-3.3054046163768,51.53911718872721],[-3.3067532597671656,51.5371219743089]]],[[[-3.3027545746036,51.53609629117226],[-3.301421719201108,51.53575436647166],[-3.3001122499934668,51.53769129298221],[-3.296142986492751,51.53662202133366],[-3.294809777950515,51.53850132698179],[-3.293496868713629,51.53813051354931],[-3.292121712748439,51.53998073199242],[-3.290819361871763,51.539595656504524],[-3.289403348187319,51.5414166182719],[-3.287936674563477,51.54322162260439],[-3.2853765744131938,51.542395148543044],[-3.2838825360674653,51.54415660436839],[-3.283882539590554,51.544156605460735],[-3.282339851677814,51.54590090254047],[-3.280747972216977,51.547628648631274],[-3.2832353898725475,51.54853764159162],[-3.2848520415687505,51.54678290474435],[-3.287364563965132,51.54766460720725],[-3.288955930635636,51.545865045530405],[-3.2902243346867017,51.546292120102045],[-3.2917766921578884,51.544461605653204],[-3.29305685232551,51.54487478505397],[-3.2945689619709984,51.543013606564706],[-3.2958604757036007,51.5434127616585],[-3.297331055619441,51.54152128453389],[-3.298633524792323,51.54190629336489],[-3.300061375045217,51.53998480726149],[-3.3000613807697987,51.53998480889862],[-3.3000615246978775,51.53998460597773],[-3.300061648970519,51.53998443874478],[-3.3000616445665876,51.53998443737995],[-3.301435099949513,51.538048084585505],[-3.301435109195865,51.53804808704012],[-3.3014353309148015,51.53804775892073],[-3.301435381657783,51.53804768752695],[-3.3014353798962595,51.53804768698104],[-3.3027545746036,51.53609629117226]]],[[[-3.301531391252313,51.51846922562661],[-3.2987401157240672,51.51802335260609],[-3.297927299941622,51.5199341653201],[-3.3007057835951823,51.52041038807222],[-3.301531391252313,51.51846922562661]]],[[[-3.3012087224089393,51.52259467503561],[-3.299826275627792,51.52234149135655],[-3.2988924294167306,51.524263480149536],[-3.3002676332855185,51.52453169053441],[-3.3012087224089393,51.52259467503561]]],[[[-3.300429465690905,51.47112200993378],[-3.299876677384325,51.46919135361843],[-3.298462296804608,51.46935727724887],[-3.2990106053799004,51.471272494806364],[-3.300429465690905,51.47112200993378]]],[[[-3.297860841192189,51.467449081881476],[-3.2972059502024367,51.46554697406899],[-3.295801802604193,51.465743593966806],[-3.2964512325001927,51.467630008244946],[-3.2922227195104194,51.468173779262465],[-3.292804785299015,51.47002118087175],[-3.294219203863351,51.46985532906567],[-3.2947539688799137,51.47172384694748],[-3.296172857193062,51.47157341331309],[-3.295633616122381,51.46968945991539],[-3.297048021634878,51.46952357314731],[-3.296451270244276,51.46763011763405],[-3.2964513572776566,51.46763036985957],[-3.297860841192189,51.467449081881476]]],[[[-3.294397783154588,51.46594058277716],[-3.293701427004324,51.46407644155374],[-3.2909045914693213,51.464500134586196],[-3.291589425815944,51.46633373216876],[-3.294397783154588,51.46594058277716]]],[[[-3.2913903525279617,51.47018701616773],[-3.2908133024628934,51.468355353958344],[-3.2908133094856336,51.46835535285111],[-3.2908132233683562,51.468355102814556],[-3.2908131918187795,51.46835500246662],[-3.290813188746585,51.46835500301961],[-3.2901850928517304,51.466529905627844],[-3.28737666141,51.466922960950534],[-3.2867093778349465,51.46513591486066],[-3.2881078442371896,51.46492412961458],[-3.287384307827808,51.46312868548479],[-3.2845997285824597,51.46358265342938],[-3.2838395520568273,51.46182597848132],[-3.281068123361402,51.462310149944656],[-3.281815095839357,51.464036554937685],[-3.2804227609439742,51.46426348046688],[-3.281115393741344,51.46598288760219],[-3.2825139079749284,51.4657711696531],[-3.2831640642458155,51.467512776910425],[-3.2845683147463984,51.46731631224839],[-3.2851750825828887,51.46908006495374],[-3.2894037627043318,51.46853655720011],[-3.2899759095007517,51.47035283412477],[-3.2913903525279617,51.47018701616773]]],[[[-3.2906417248926534,51.52265350386927],[-3.289266632409053,51.52238518359176],[-3.2883329348950836,51.5241917206214],[-3.284230761283584,51.52334184161874],[-3.2851414514993915,51.521580126533976],[-3.2837664232317345,51.521311742016195],[-3.282863217818765,51.52305887611482],[-3.282863219579477,51.52305887666101],[-3.2819115821661913,51.52479521054463],[-3.2791931033238804,51.52419880520862],[-3.2782097777800017,51.52589531071599],[-3.276859074196047,51.52558232141124],[-3.2758372643858324,51.52725321036156],[-3.2744954971652676,51.52692553654849],[-3.2734363639174626,51.52857044469723],[-3.270771561313455,51.52788591573269],[-3.2696859693221283,51.52949010491763],[-3.2683633368450637,51.52913335243129],[-3.2672428645061706,51.530711435984315],[-3.269867850300298,51.531453642850764],[-3.2686824380520023,51.533047463787646],[-3.26998441259134,51.53343278812903],[-3.268743721640165,51.53502735718362],[-3.267458175855293,51.53660857592244],[-3.268737889279079,51.53702201923803],[-3.270034618188922,51.53542699034204],[-3.2700346239127467,51.535426992254926],[-3.2700347881659746,51.535426781390804],[-3.2700348683346,51.535426682544276],[-3.2700348656921614,51.535426681450396],[-3.271286413516726,51.53381809908892],[-3.2712864187996993,51.53381810072802],[-3.271286558487273,51.53381791293386],[-3.271286671046952,51.533817768238286],[-3.2712866670842793,51.53381776687182],[-3.272492926305972,51.532195792258065],[-3.273805495882116,51.532566844901964],[-3.2725884421503317,51.53420339748793],[-3.2738904874861694,51.53458868004804],[-3.275118322354458,51.53293756599679],[-3.273805729253845,51.53256653080873],[-3.2738056281107784,51.53256666671467],[-3.2749764726972637,51.530917304320404],[-3.2749764801807446,51.53091730650507],[-3.2749766648579732,51.530917033616724],[-3.2749767125696945,51.530916966352656],[-3.27497671080877,51.530916965806355],[-3.276101033440626,51.52925525949321],[-3.2800985143979005,51.530281860862694],[-3.278944747153586,51.531987304504966],[-3.281590354348,51.53270055973792],[-3.280368670486106,51.534421888589364],[-3.2829940875127943,51.53516380238974],[-3.284236051273269,51.533413757138355],[-3.2842360596371996,51.533413759321235],[-3.2842362608952707,51.53341346198334],[-3.284236305969749,51.533413398285006],[-3.284236304208622,51.53341339773884],[-3.285428769149896,51.53165045089856],[-3.286761382671355,51.53199256024578],[-3.2855589393280504,51.533770335890594],[-3.2868818401288373,51.53412689724795],[-3.2880942650780542,51.53233428598314],[-3.28676162940033,51.53199219481487],[-3.2867613909800344,51.531992547068036],[-3.2879140418532877,51.53020158683473],[-3.285230178217522,51.529546499592115],[-3.2863141942815433,51.527773291013844],[-3.2863141995633827,51.52777329237797],[-3.2863143101357752,51.52777310131465],[-3.2863144176849115,51.52777292561604],[-3.2863144128428217,51.5277729242513],[-3.287348566844673,51.52598817930163],[-3.2887079090007147,51.52628629951607],[-3.2897003720644458,51.52447498408043],[-3.2897003597424823,51.52447498135498],[-3.2906417248926534,51.52265350386927]],[[-3.282763833423946,51.530965827371],[-3.2814312780949053,51.53062367530658],[-3.282546179936144,51.52889168252083],[-3.2838880614237755,51.529219267168735],[-3.282763833423946,51.530965827371]],[[-3.2761012552447606,51.52925491523785],[-3.2747688086147706,51.528912690137794],[-3.274768582808892,51.52891302369831],[-3.275837239456579,51.527253250989716],[-3.275837068891129,51.52725352989573],[-3.277178846591703,51.52758118973062],[-3.2761012552447606,51.52925491523785]],[[-3.273653994316927,51.530560273052465],[-3.2723312984976376,51.53020356509095],[-3.273436146034442,51.52857078290808],[-3.2747685797470205,51.528913028639586],[-3.273653994316927,51.530560273052465]]],[[[-3.2873849446386623,51.520062103649884],[-3.2860026512133262,51.519808756977255],[-3.285141275595982,51.52158048994427],[-3.2865163182911865,51.521848861629785],[-3.2873849446386623,51.520062103649884]]],[[[-3.2823914115680166,51.52104334109557],[-3.2810164160711,51.52077492432165],[-3.2801285573884784,51.5224921723278],[-3.2814958791173097,51.522775532058866],[-3.2823914115680166,51.52104334109557]]],[[[-3.268039683192168,51.51311436482603],[-3.2652490560598153,51.51266769380919],[-3.2658645786573453,51.51111438568215],[-3.2644632983855955,51.510906261427095],[-3.2638537658218207,51.512444333802335],[-3.261063233325376,51.51199756506631],[-3.260423443347005,51.51349871485506],[-3.261812280510714,51.513737277826415],[-3.261123879626418,51.51524596029626],[-3.260393272749355,51.5167470439246],[-3.2658923758927343,51.5178215944073],[-3.266652007854612,51.51626049107757],[-3.266652009174374,51.516260491350245],[-3.266652034061804,51.51626043673494],[-3.266652153748408,51.51626019081952],[-3.266652147149595,51.516260189456126],[-3.2673677975562048,51.514691368999706],[-3.267367804593564,51.51469137008822],[-3.267367911505936,51.514691118701805],[-3.2673679442519066,51.51469104734464],[-3.267367942491704,51.5146910467982],[-3.268039683192168,51.51311436482603]]],[[[-3.265047331087454,51.50454806461478],[-3.263626403999646,51.50440143074483],[-3.2631989785720523,51.50593135810492],[-3.2617823346626027,51.50576924842497],[-3.261316766885152,51.50727918616017],[-3.2641406601677376,51.507634215233324],[-3.2646155394578877,51.50609376814499],[-3.2646155456139425,51.50609376896045],[-3.264615605605854,51.50609355466276],[-3.2646156373714326,51.50609345121478],[-3.264615634732522,51.50609345066938],[-3.265047331087454,51.50454806461478]]],[[[-3.2642427150162314,51.520917443892465],[-3.261524693533529,51.52032062797213],[-3.2606518416311285,51.52182553464567],[-3.263352827634462,51.52245187919639],[-3.2642427150162314,51.520917443892465]]],[[[-3.259773974730324,51.49046503033577],[-3.259769514361089,51.488974463086464],[-3.255450552128475,51.489002955685756],[-3.2554547340236413,51.49044617526324],[-3.249695758463139,51.490421169375885],[-3.2496609834575465,51.491801736921175],[-3.251100372499068,51.49182370374083],[-3.2510262280903235,51.49321907702875],[-3.2553420533944557,51.493331908712236],[-3.255418561096607,51.49188949737716],[-3.2568579644513176,51.49191139352996],[-3.256894479167028,51.490452662746584],[-3.259773974730324,51.49046503033577]]],[[[-3.2591229176143126,51.48153662350705],[-3.258868979749208,51.48005450883897],[-3.254579265257543,51.48036465218259],[-3.2548250005702957,51.48179967735671],[-3.2519597345964986,51.4819751615609],[-3.252160820762031,51.483381689633404],[-3.253595733065252,51.48330958175982],[-3.2537592937143955,51.4847334605837],[-3.2580695469444176,51.48456396617894],[-3.2579004559898275,51.48309315251181],[-3.257900460821481,51.483093152231966],[-3.2579004361509165,51.483092980001715],[-3.2579004235255633,51.483092870023086],[-3.25790042045117,51.48309287030092],[-3.257690298149075,51.48162442399021],[-3.2591229176143126,51.48153662350705]]],[[[-3.2370575251006684,51.446068427634955],[-3.2358170306906313,51.44483215442271],[-3.234605120934215,51.445316253789066],[-3.233344581618449,51.44410729685881],[-3.2333445842494526,51.444107295759125],[-3.233344478228016,51.44410719765995],[-3.233344337881274,51.444107063111346],[-3.233344334373717,51.44410706476045],[-3.232050260027249,51.44291199739693],[-3.2320502934570974,51.442912027264406],[-3.2332343960625627,51.44240182200474],[-3.2318922694040824,51.441207767421986],[-3.2307226134117264,51.44173079878541],[-3.232050020302428,51.442911784215426],[-3.230865900090519,51.44342197746064],[-3.232146266024495,51.44460441358133],[-3.2333932537399863,51.445800403612715],[-3.2346069668939,51.44701005082634],[-3.2370575251006684,51.446068427634955]]],[[[-3.2365515225484964,51.54213850645648],[-3.235391517956827,51.541604992737504],[-3.2339256280280284,51.54282186082797],[-3.23507053382027,51.543367934201655],[-3.2365515225484964,51.54213850645648]]],[[[-3.2321591841725437,51.47338250996534],[-3.231682270771058,51.472195465008454],[-3.2302894369610162,51.472421795160926],[-3.2307602027258646,51.473593627303956],[-3.2321591841725437,51.47338250996534]]],[[[-3.231672449727352,51.43949327825778],[-3.2302495197065118,51.4383167436149],[-3.230249753190856,51.43831692965032],[-3.2313894120078803,51.43776871352048],[-3.2299183517277945,51.436595467587296],[-3.22879418888284,51.43715602012401],[-3.230249449352495,51.43831668717366],[-3.2291097676455762,51.43886489408659],[-3.230517616286519,51.440028982812294],[-3.231672449727352,51.43949327825778]]],[[[-3.228896683874253,51.4726483311319],[-3.228399838272377,51.47149694997292],[-3.2270135450739357,51.47173838810745],[-3.227503821606495,51.472874625440156],[-3.228896683874253,51.4726483311319]]],[[[-3.2247676444344577,51.53845264061804],[-3.222478346507599,51.537360286466566],[-3.2211330568259835,51.53843708352752],[-3.2233914500159546,51.5395542183442],[-3.2247676444344577,51.53845264061804]]],[[[-3.222854675668012,51.4724628275924],[-3.2223537146617454,51.47137731614356],[-3.2209743482270055,51.47163375649805],[-3.220450768411644,51.470569284721535],[-3.2218227352530406,51.470297857282326],[-3.221261585414106,51.46922414022785],[-3.2212615871696477,51.4692241396778],[-3.2212615527797066,51.46922407744105],[-3.2212614703150595,51.46922392033761],[-3.2212614659250036,51.469223921164186],[-3.220670425395957,51.46815648095729],[-3.2193145210500083,51.46845763656992],[-3.2187025383502146,51.46741172823063],[-3.2200497962170513,51.46709584876431],[-3.219399395520678,51.46604171897702],[-3.218061194672796,51.466372232298],[-3.2173910738130673,51.46534009497484],[-3.2173912063968686,51.465340289345235],[-3.220048654041101,51.46465019635361],[-3.2193300905907605,51.46359666664936],[-3.219330091468444,51.463596666374336],[-3.2193300685662116,51.4635966343003],[-3.2193299434669305,51.463596450623506],[-3.219329938200293,51.46359645199932],[-3.2185822275693172,51.46255111337289],[-3.221199325699823,51.461803482317194],[-3.220401074545397,51.46073784958381],[-3.2204010767391513,51.46073784875909],[-3.2204010106936485,51.46073776405587],[-3.220400910293205,51.46073763000756],[-3.2204009063436496,51.460737631108024],[-3.2195732015570657,51.459681059820134],[-3.2208599895232664,51.45927894145063],[-3.21999131532131,51.45821738931913],[-3.219991315759801,51.458217389044464],[-3.2199912928708647,51.45821736218219],[-3.2199911365929577,51.45821717085138],[-3.21999113132812,51.45821717277582],[-3.2190931463025225,51.457165447522016],[-3.2190931484961234,51.457165446697324],[-3.219093080720388,51.45716537077227],[-3.219092974203711,51.457165245779635],[-3.21909297069407,51.45716524715399],[-3.2181655922240306,51.456123251075624],[-3.2169143217374314,51.456566862097375],[-3.217829478612018,51.45759515720181],[-3.216565962791385,51.45802505360324],[-3.2156632135807706,51.45701066887471],[-3.215663214019273,51.45701066860006],[-3.215663199058857,51.45701065270259],[-3.2156630274320355,51.45701045945804],[-3.2156630221671456,51.4570104613823],[-3.214732127267953,51.456006081244965],[-3.2147321316551998,51.456006079595745],[-3.214731972825872,51.45600591486679],[-3.2147319345450454,51.45600587347772],[-3.2147319336680353,51.456005874027],[-3.213773150834466,51.455011774383145],[-3.2137731539050565,51.45501177300929],[-3.2137730373267583,51.455011656522785],[-3.213772966495159,51.45501158334],[-3.213772964740608,51.45501158416425],[-3.212786395905433,51.45402787836511],[-3.211573999595481,51.45451173859854],[-3.2105740652816595,51.453552263697986],[-3.2105740674752443,51.45355226287346],[-3.210573986990227,51.453552188598735],[-3.2105738717524313,51.453552078142614],[-3.2105738686813106,51.453552079242094],[-3.2095473296504293,51.45260364650714],[-3.2095473300889243,51.45260364623253],[-3.2095473050226317,51.45260362403287],[-3.2095471181112907,51.45260345136291],[-3.2095471132856828,51.45260345328656],[-3.208494293681064,51.45166636049798],[-3.2084942954355786,51.451666359673816],[-3.2084942061723813,51.45166628293536],[-3.208494076887212,51.451666167824335],[-3.2084940733776732,51.45166616919838],[-3.2074152651285104,51.450740674380874],[-3.2085706425799843,51.450205197035345],[-3.2074506081755727,51.44927872601996],[-3.205170122260398,51.45037468550091],[-3.2040558523984024,51.449485597320425],[-3.2040558541524344,51.449485596222026],[-3.2040557666734113,51.4494855288051],[-3.204055641383712,51.44948542904709],[-3.2040556387521835,51.4494854304204],[-3.202916510424299,51.44860843380451],[-3.2018075418484044,51.44918090731573],[-3.200660320628346,51.44832846613263],[-3.197381743049494,51.450081809175295],[-3.198480700453048,51.45089843202408],[-3.199589750933273,51.45032599508592],[-3.200681287825405,51.451166398898444],[-3.2017492440268387,51.452018583371526],[-3.2027933684580145,51.452882352095145],[-3.2038132736570346,51.453757390525226],[-3.204808627003041,51.4546434145252],[-3.2057788895086836,51.455539937942525],[-3.2045800821565873,51.45603687084229],[-3.205511829669965,51.456931030639076],[-3.2067243519709954,51.456447237954464],[-3.2076440674658038,51.45736440626712],[-3.208538278301319,51.45829168441286],[-3.2110158895841843,51.45737748344871],[-3.2119089928285165,51.458341173690144],[-3.2106576063193057,51.45878471585095],[-3.21151175937576,51.45974461375326],[-3.212338945555813,51.46071360703338],[-3.213138855535764,51.46169132701568],[-3.213911517083318,51.46267781235948],[-3.2152094708420713,51.462289849028274],[-3.215964912651447,51.463298486611755],[-3.2146562927156173,51.463672253272456],[-3.215373345206578,51.46467485039051],[-3.212735517755461,51.46539370727178],[-3.212735633583696,51.46539387697344],[-3.212038992214005,51.46441974133589],[-3.210730312087895,51.464793462737525],[-3.2100175145273084,51.46384161874507],[-3.2100175167213942,51.46384161792054],[-3.210017454212545,51.463841537871964],[-3.210017367923885,51.46384142273092],[-3.2100173644124017,51.46384142355642],[-3.209278110462111,51.462897488583586],[-3.2092781130947685,51.46289748748447],[-3.209278032989516,51.46289738934477],[-3.209277968723671,51.462897307651296],[-3.2092779665290974,51.46289730820151],[-3.2085124823689264,51.46196157069717],[-3.208512483246648,51.461961570422226],[-3.208512461243349,51.46196154492858],[-3.208512324806844,51.461961378254955],[-3.2085123204182837,51.46196137962964],[-3.207720689557966,51.46103394682295],[-3.206456983826482,51.46146373403676],[-3.2072367811756775,51.46237733312877],[-3.20596122269176,51.46279327134895],[-3.2067040388073464,51.463701361324965],[-3.2074213486184777,51.464617342397716],[-3.208113016702784,51.46554104177332],[-3.2094217369640714,51.4651673534708],[-3.2100977126815007,51.466112667855334],[-3.210747237446329,51.46706525839076],[-3.212076066225651,51.46672029605743],[-3.2127083397025107,51.46769431470418],[-3.214046613948207,51.4673638663868],[-3.214660538082218,51.4683590661014],[-3.213313213273907,51.46867488549624],[-3.213890847177831,51.46966228961013],[-3.215246819428655,51.46936120088817],[-3.21580492539461,51.47036937654603],[-3.2171691151354986,51.47008309122312],[-3.2177067095367247,51.4711119145486],[-3.2182155901241263,51.4721466279813],[-3.2186955487578994,51.47318683378094],[-3.219146356845205,51.47423204313956],[-3.220539302233143,51.47400584814757],[-3.220081954033288,51.472945550247545],[-3.222854675668012,51.4724628275924]],[[-3.2160623115624496,51.46568510555795],[-3.215373471155693,51.464675026390495],[-3.2153734922941455,51.46467505599799],[-3.216692378677418,51.464315607737475],[-3.2173910628010676,51.46534007880013],[-3.2160623115624496,51.46568510555795]],[[-3.212254471766347,51.45692016085463],[-3.2113216348973928,51.455952909112014],[-3.2125474056883663,51.455482348173206],[-3.2134932148707964,51.456463023864835],[-3.212254471766347,51.45692016085463]],[[-3.212075936284157,51.46672009591881],[-3.2120759446532703,51.46672010880444],[-3.211416705294197,51.465753312193684],[-3.2127356551636868,51.46539390850116],[-3.2134047476449483,51.46637511411822],[-3.212075936284157,51.46672009591881]],[[-3.207936660806959,51.45596324246485],[-3.206977861696402,51.455043168432766],[-3.2081766222570764,51.45454621257667],[-3.209149132704333,51.455479420236934],[-3.207936660806959,51.45596324246485]]],[[[-3.2082898248788383,51.554267999527774],[-3.2073714472424952,51.553575123700426],[-3.205573932415067,51.55448465673884],[-3.2037509085137734,51.55537458793507],[-3.201903824168089,51.556244216238554],[-3.201903821524654,51.55624421432027],[-3.200032258816615,51.55709374076065],[-3.199193731670927,51.556362560430905],[-3.1973188294315147,51.55718271088737],[-3.198136740330274,51.557922919722735],[-3.2000320477654633,51.5570938369018],[-3.200031873647451,51.557093915738236],[-3.2008704263686054,51.55782509437696],[-3.202762372427609,51.55696630840521],[-3.2027623794785796,51.556966314434874],[-3.2046298935050874,51.556087059914354],[-3.204629903616674,51.55608705497005],[-3.2046302799554067,51.55608687778911],[-3.2046302751078546,51.55608687367809],[-3.2064724398109674,51.55518759045623],[-3.206472444217077,51.555187593744606],[-3.2064727466638665,51.55518744074811],[-3.2064728152440716,51.55518740723554],[-3.2064728143625523,51.55518740641329],[-3.2082898248788383,51.554267999527774]]],[[[-3.183467080204134,51.489454550344554],[-3.1834477899848483,51.488795807153394],[-3.1834477917419663,51.488795806878194],[-3.1834477885963715,51.48879575394058],[-3.1834477862903423,51.48879567137849],[-3.1834477840934925,51.48879567137962],[-3.183410082415561,51.48813723849907],[-3.1819717961203358,51.4881771779033],[-3.1820085843851196,51.48882000997718],[-3.182027396075139,51.48946312586259],[-3.183467080204134,51.489454550344554]]],[[[-3.1709129060117642,51.44279030937524],[-3.1695922581644043,51.44227533550471],[-3.1688415361782964,51.44304200412531],[-3.170140896799733,51.44354868041476],[-3.1709129060117642,51.44279030937524]]],[[[-3.1664341632505586,51.50390229413877],[-3.1651563023317495,51.503487736107076],[-3.165156273355379,51.503487770130164],[-3.1655324463838785,51.503026352348606],[-3.164243219945809,51.50262576573476],[-3.163878464162948,51.50307316385138],[-3.1626006505029225,51.50265857819444],[-3.1622352120954194,51.50308807389354],[-3.160969203850639,51.502659616774416],[-3.1606039644640105,51.50307134392267],[-3.1593501445358885,51.502629144049],[-3.159350122579705,51.50262916791955],[-3.1597031433818903,51.50223122717576],[-3.1609691274474656,51.50265970155658],[-3.1613228591534104,51.50224397831457],[-3.160045090554203,51.501829364486376],[-3.1603756121711424,51.50142399890947],[-3.1603756134900634,51.50142399945765],[-3.1603756551982443,51.50142394623094],[-3.1603756758334796,51.501423920714856],[-3.1603756749542797,51.50142392044083],[-3.160694846760993,51.5010149626273],[-3.160694806372701,51.50101501667659],[-3.161994960515245,51.50140154212586],[-3.1623133275873005,51.500974831849746],[-3.1610025702046833,51.500602533391415],[-3.1610025517666696,51.500602557809735],[-3.161298690122333,51.50018683914384],[-3.15997775255393,51.49982886366033],[-3.1599777450914934,51.49982887436023],[-3.1602523962232887,51.49942445836967],[-3.158921679868632,51.49908091382791],[-3.1586568356823386,51.499470873005386],[-3.157335939508274,51.49911286772798],[-3.1575909839628524,51.498737354443726],[-3.157590981325637,51.498737353895905],[-3.157835480334244,51.49835912296968],[-3.157835438198367,51.49835919183052],[-3.1591755267677293,51.498688204033144],[-3.1594184468415962,51.49829263120868],[-3.158069370469004,51.49797827268388],[-3.1580693489624827,51.49797830780021],[-3.158292565489367,51.497594941646994],[-3.156934933167962,51.4972953135677],[-3.1567203122180136,51.497663898352016],[-3.1553712738475053,51.497349509035864],[-3.15515531851262,51.497701121714876],[-3.1564953671682505,51.4980301640257],[-3.156260305429215,51.49839377912125],[-3.1549296513008707,51.49805018977894],[-3.1546941995944313,51.49839682620205],[-3.1544490940349976,51.49874083614043],[-3.154194418305586,51.49908210266166],[-3.1528943948320736,51.49869549361971],[-3.152641184828436,51.499019863324335],[-3.152378915888105,51.499341454468045],[-3.1536565413959847,51.49975615444783],[-3.1533735739424245,51.50008864684858],[-3.1533735748215697,51.500088647122645],[-3.1530813878571977,51.50041796154637],[-3.1527799491844077,51.50074413359133],[-3.154021091428691,51.50119999467964],[-3.1536976890727373,51.50153623794036],[-3.1549259277563966,51.502005539584914],[-3.154579875066384,51.50235150585691],[-3.1542242065210098,51.502693637618215],[-3.153859020380733,51.50303184204421],[-3.1526718026058824,51.50252305736218],[-3.1523115549214804,51.502844426031956],[-3.1519423855346465,51.50316180751632],[-3.151564452057467,51.50347506865984],[-3.1511776898352433,51.50378426123591],[-3.1534326576526217,51.504902748332675],[-3.153850270342549,51.504568879708785],[-3.1561362775908712,51.505662584269295],[-3.156574721512309,51.50529913760219],[-3.157732880477776,51.505833435791764],[-3.158175931647355,51.50545249542994],[-3.1570030084480636,51.504930899172024],[-3.1574207524221256,51.50455820466614],[-3.1574207528618516,51.5045582049403],[-3.1574207660382094,51.50455819259312],[-3.1574208393891596,51.504558127289215],[-3.157420837630449,51.50455812646683],[-3.157828093684232,51.50418093291591],[-3.157828095003207,51.50418093346411],[-3.1578281376051027,51.50418089230709],[-3.157828172741883,51.504180859929754],[-3.1578281718624264,51.504180859381414],[-3.1582248136465427,51.503799285153306],[-3.1594398506553296,51.50428184696629],[-3.1598391589596004,51.50388259604333],[-3.159839137441489,51.50388261854234],[-3.1610675027223327,51.50435185596292],[-3.1614685615928098,51.50393480288769],[-3.1602272663630333,51.5034790230128],[-3.1606039486564055,51.503071361756966],[-3.1606038946471045,51.50307142266795],[-3.1618577370909513,51.50351361185031],[-3.1622351370161637,51.5030881630648],[-3.1635011690103743,51.5035166103295],[-3.1638784452847424,51.50307318689882],[-3.1638783895283957,51.50307325521792],[-3.1651562228662202,51.503487829396214],[-3.1647672251888648,51.5039450441725],[-3.1660333051119607,51.504373464319194],[-3.1664341632505586,51.50390229413877]],[[-3.160694869589013,51.501014931898816],[-3.1593947361292565,51.500628395031676],[-3.1593947159337485,51.50062842109606],[-3.1596918117928285,51.5002302507165],[-3.1596917788690413,51.50023029708283],[-3.1610025113795652,51.50060261240773],[-3.160694869589013,51.501014931898816]],[[-3.159839212096719,51.50388254116747],[-3.1586108694142525,51.503413293590846],[-3.158985995020029,51.50302322969318],[-3.1577447493220796,51.502567423477316],[-3.158096281250363,51.50218700317457],[-3.159350075154159,51.50262921922758],[-3.1589859203679955,51.50302331118194],[-3.1602271855606525,51.50347910615008],[-3.159839212096719,51.50388254116747]],[[-3.1596918337421727,51.50023021998848],[-3.158381119519075,51.49985789246383],[-3.1586568312925363,51.499470879315524],[-3.158656781249995,51.499470952842074],[-3.159977693730706,51.49982894596769],[-3.1596918337421727,51.50023021998848]],[[-3.159086539383553,51.50102327704264],[-3.157797425541026,51.50062261944979],[-3.158094562759993,51.500241922195876],[-3.159394672468743,51.500628476243044],[-3.159086539383553,51.50102327704264]],[[-3.1578354829680877,51.49835911912876],[-3.156495411942199,51.498030094616],[-3.156720291589711,51.497663933742146],[-3.156720267889077,51.49766397434479],[-3.1580693221887812,51.497978351147],[-3.1578354829680877,51.49835911912876]],[[-3.1546572437156266,51.503887672430196],[-3.1534844017413235,51.50336604112452],[-3.153858954056649,51.503031903503924],[-3.1538589443936074,51.503031912558235],[-3.155046187734093,51.503540688746625],[-3.1546572437156266,51.503887672430196]]],[[[-3.1641382723425333,51.50949010211992],[-3.162995061062682,51.50894333664965],[-3.162453632665514,51.50937626048859],[-3.1635813788293063,51.509935400691575],[-3.1641382723425333,51.50949010211992]]],[[[-3.162633792059036,51.50616368135733],[-3.1602309302579217,51.505172295473976],[-3.1597954290306878,51.50557566794736],[-3.1593487880729407,51.50597416085919],[-3.160521760885836,51.50649573603765],[-3.1609827988746324,51.50608438876526],[-3.1621702004329846,51.506593092580836],[-3.162633792059036,51.50616368135733]]],[[[-3.161851877150382,51.50839655998736],[-3.160708720604972,51.50784977213351],[-3.1601982231639445,51.508257947946866],[-3.1613259141107095,51.50881710966496],[-3.161851877150382,51.50839655998736]]],[[[-3.1595655918656242,51.50730297336278],[-3.1584224900521405,51.506756163127314],[-3.1579429240915657,51.50713959155492],[-3.1590705598243143,51.5076987750605],[-3.1595655918656242,51.50730297336278]]],[[[-3.1388075208718114,51.48919486740104],[-3.138788180364379,51.48902273111564],[-3.13878818080375,51.48902273111558],[-3.1387881781617373,51.48902271438386],[-3.1387881763986583,51.4890226982006],[-3.1387881759592866,51.48902269820066],[-3.1387640311718803,51.48885083410638],[-3.1387640316112493,51.48885083410632],[-3.1387640294103294,51.48885082231187],[-3.138764026326599,51.488850798722794],[-3.1387640254478564,51.4888507987229],[-3.1387350746713873,51.48867919118215],[-3.1373046366252053,51.48878112466292],[-3.1373309546015684,51.48893715200981],[-3.1373529023864895,51.489093412759246],[-3.1373704825764794,51.48924992199966],[-3.1388075208718114,51.48919486740104]]],[[[-3.136505999660156,51.415676193711256],[-3.1344351159271846,51.41554031448968],[-3.1344351163650295,51.41554031119806],[-3.1344348022747752,51.41554029394782],[-3.134434690412711,51.415540286552634],[-3.1344346899743862,51.41554028792418],[-3.1323603401400955,51.41542699195771],[-3.1323603392639963,51.415426997992384],[-3.130283579668248,51.41533633880241],[-3.130283579667681,51.41533633523652],[-3.1302832774255362,51.41533632538051],[-3.1302831528436443,51.415336319902245],[-3.1302831524051964,51.41533632127377],[-3.1282041848411457,51.41526830283078],[-3.1282041852791873,51.415268297344774],[-3.126123155818234,51.41522292941023],[-3.12612315581814,51.41522292803873],[-3.1261230417653003,51.41522292694455],[-3.1261227847075213,51.415222921191116],[-3.126122784707708,51.415222923934124],[-3.124041236999142,51.41520024176287],[-3.1240412369991044,51.41520024011707],[-3.1240411102251318,51.415200240118196],[-3.124040808862707,51.41520023682928],[-3.124040808862782,51.41520024012088],[-3.121959002573211,51.41520024012087],[-3.1219590021346213,51.41520023682926],[-3.1219587007721965,51.41520024011818],[-3.121958573998224,51.41520024011705],[-3.1219585739981865,51.41520024176285],[-3.119877083316021,51.41522292366129],[-3.1198770833161515,51.41522292174118],[-3.1198769087274263,51.415222925576735],[-3.1198766547405046,51.41522292831297],[-3.119876655178982,51.415222931055965],[-3.11779605385603,51.415268289134694],[-3.1177960534173965,51.415268288860375],[-3.117796028852068,51.41526828968218],[-3.1177956827446085,51.415268297072906],[-3.1177956831828375,51.415268300913134],[-3.1157166585920826,51.41533632154795],[-3.115716658153678,51.41533631990211],[-3.115716521727728,51.41533632592824],[-3.1157162308909316,51.41533633551065],[-3.1157162313290745,51.41533633880228],[-3.113639472171988,51.41542699799226],[-3.1136394712958326,51.41542699223188],[-3.1115651210229274,51.415540287923974],[-3.1115651210232698,51.41554028655248],[-3.1115649964396423,51.415540294769315],[-3.1115646946322837,51.41554031119786],[-3.1115646955087968,51.41554031448955],[-3.1094942381701562,51.41567616578163],[-3.1094942372937897,51.41567616248994],[-3.1094939284639893,51.41567618604412],[-3.109493811337154,51.41567619371103],[-3.1094938117754167,51.41567619508257],[-3.1074273917434563,51.41583458848424],[-3.1074273913050665,51.41583458766129],[-3.1074273206770906,51.41583459368649],[-3.107427022812105,51.41583461668809],[-3.10742702368833,51.41583461997981],[-3.105364898529631,51.416015541205404],[-3.1053648998438534,51.4160155458687],[-3.103308599353434,51.41621885990315],[-3.1033085989157017,51.41621885770866],[-3.103308405889945,51.4162188790716],[-3.103308176014983,51.41621890179986],[-3.103308176891271,51.41621890426871],[-3.1012578450921304,51.41644459113825],[-3.1012578433401616,51.416444585377626],[-3.0992132605943463,51.41669267936988],[-3.099213262346352,51.41669268458193],[-3.0971766728238244,51.41696291328807],[-3.0971766723859204,51.416962911916485],[-3.097176535506278,51.41696293163589],[-3.09717630956865,51.41696296148478],[-3.0971763104446235,51.41696296395368],[-3.095147551200911,51.41725534145842],[-3.09514754901066,51.41725533624621],[-3.093126454044182,51.41756989418823],[-3.093126456234397,51.417569899126185],[-3.091115248482241,51.41790629096689],[-3.0911152462920373,51.41790628630319],[-3.0891132886686976,51.4182646144044],[-3.089113286916357,51.41826461111229],[-3.0891130004088847,51.41826466588938],[-3.089112878435846,51.41826468779809],[-3.0891128788735265,51.41826468916972],[-3.087121551240673,51.418644706451474],[-3.087121550802409,51.41864470590275],[-3.0871214889362193,51.41864471850159],[-3.0871211949652757,51.418644774368715],[-3.087121196717491,51.418644777660866],[-3.0851407452949937,51.41904643427169],[-3.085140744418727,51.41904643289991],[-3.0851406176116876,51.41904646001462],[-3.085140339428533,51.41904651643047],[-3.0851403411806158,51.41904651972263],[-3.083171418548912,51.419469681476606],[-3.0831714181111614,51.41946968037926],[-3.0831713163115486,51.419469703385836],[-3.0831710139872293,51.419469768292096],[-3.0831710161778925,51.419469771584446],[-3.0812141701308176,51.41991432075753],[-3.0812141688164467,51.419914318836966],[-3.0812139941718457,51.41991436074253],[-3.081213766434662,51.41991441222966],[-3.081213768187003,51.419914414973285],[-3.079269588286961,51.42038021715758],[-3.07926958653473,51.42038021441393],[-3.079269354399095,51.420380273027426],[-3.079269190282438,51.42038031246533],[-3.07926919159674,51.420380314385916],[-3.0773382174679047,51.4208672412237],[-3.077338215277871,51.42086723765694],[-3.077337903266482,51.42086732037385],[-3.077337870793116,51.420867328590184],[-3.077337871231556,51.42086732886465],[-3.075420742202217,51.42137522107463],[-3.075420741325915,51.42137521997707],[-3.0754206500449692,51.42137524544989],[-3.075420348559157,51.421375325148674],[-3.0754203511880727,51.42137532844132],[-3.0735177001068545,51.42190401662579],[-3.0735176979161807,51.421904013881864],[-3.0735174547839255,51.4219040848228],[-3.073517306010272,51.42190412617923],[-3.0735173073246824,51.421904127825584],[-3.0716296569893884,51.42245346589046],[-3.0716296552372215,51.422453463421],[-3.0716294252623624,51.42245353326658],[-3.0716292712164197,51.42245357818418],[-3.0716292725307746,51.42245357983056],[-3.0697571976235647,51.42302340143533],[-3.0697571958711536,51.42302339924012],[-3.0697570040713984,51.42302346004748],[-3.0697568105186024,51.42302351920824],[-3.0697568122710135,51.42302352140344],[-3.0679008327182937,51.42361366816681],[-3.067900830089787,51.42361366487397],[-3.0679005206513543,51.4236137673165],[-3.06790050177808,51.423613773342225],[-3.0679005022164967,51.42361377361673],[-3.0660612284651307,51.42422405403837],[-3.0660612267128564,51.424224051843126],[-3.066061045869548,51.424224114569846],[-3.0660608505450777,51.42422417920962],[-3.0660608527361033,51.42422418140509],[-3.064238896231541,51.42485439084842],[-3.064238895793136,51.42485439057389],[-3.064238875600828,51.42485439796986],[-3.064238520489115,51.42485452067799],[-3.0642385239938887,51.42485452479423],[-3.0624343896353565,51.425504488223154],[-3.0624343865683614,51.425504484929974],[-3.062434091573239,51.425504595594504],[-3.0624340165088952,51.42550462271136],[-3.0624340173853297,51.42550462353471],[-3.0606481943789747,51.426174165653364],[-3.0606481939405756,51.42617416537883],[-3.060648161015719,51.42617417797909],[-3.0606478818154836,51.42617428288723],[-3.0606478848827594,51.42617428590616],[-3.0588809722989407,51.42686318432151],[-3.058880970984158,51.426863183223595],[-3.058880863862187,51.426863226504295],[-3.058880607037801,51.42686332675736],[-3.0588806101054007,51.42686332950205],[-3.0571332018792594,51.4275713511458],[-3.057133196620964,51.42757134620547],[-3.0554054065012166,51.42829845059665],[-3.05540540562444,51.42829845004754],[-3.055405366107879,51.4282984673056],[-3.055405050421865,51.42829860015871],[-3.0554050543657065,51.42829860372689],[-3.0536981219450383,51.42904426881831],[-3.053698119753961,51.42904426689692],[-3.0536979643128466,51.429044337574226],[-3.05369777067569,51.42904442194393],[-3.053697773305145,51.42904442413989],[-3.0520118143332957,51.4298085949507],[-3.0520118108275454,51.42980859193129],[-3.052011541205042,51.42980871876855],[-3.0520115144192923,51.429808730821485],[-3.0520115148572478,51.42980873137035],[-3.0503470939329818,51.43059115432692],[-3.0503470934946018,51.430591154052344],[-3.0503470741720986,51.43059116336654],[-3.0503467492087966,51.43059131622409],[-3.0503467535912505,51.43059131979269],[-3.048704416134104,51.43139173803659],[-3.0487044117517037,51.43139173446792],[-3.048704111792142,51.431391885965255],[-3.048704081489599,51.43139190075821],[-3.04870408192753,51.43139190130708],[-3.0470842812744796,51.43221009277516],[-3.047084280836104,51.43221009250058],[-3.0470842689773168,51.43221009880177],[-3.047083948804179,51.43221026043092],[-3.047083953625362,51.432210263999934],[-3.045487190235196,51.43304597677372],[-3.045487187167538,51.43304597430299],[-3.0454869829217057,51.43304608498478],[-3.0454868594994995,51.43304614963777],[-3.0454868616913546,51.433046151010714],[-3.0439135751074415,51.43389915673403],[-3.0439135689712185,51.433899152341105],[-3.042363683155852,51.434769505667944],[-3.0423636914838865,51.434769511433956],[-3.0408389131421,51.43565625333889],[-3.0408389065670693,51.435656248945484],[-3.0393387531382374,51.43655965490515],[-3.03933875226151,51.43655965435593],[-3.03933872458121,51.43655967161701],[-3.039338442956204,51.43655984120711],[-3.0393384477777436,51.43655984450215],[-3.0378639898135016,51.43747926970662],[-3.0378639884983962,51.43747926888277],[-3.037863926542221,51.43747930888542],[-3.0378636888336237,51.43747945710853],[-3.03786369321729,51.43747945985471],[-3.036415031788487,51.43841483731602],[-3.036415025212459,51.43841483347098],[-3.0349923954822375,51.43936602003799],[-3.0349923928514904,51.439366018664515],[-3.0349922750709073,51.43936610004287],[-3.034992102801905,51.43936621539336],[-3.034992106309388,51.43936621731609],[-3.0335964849392805,51.44033255882279],[-3.0335964818706955,51.44033255690035],[-3.033596328477629,51.44033266705152],[-3.033596195309511,51.44033275911437],[-3.0335961979397243,51.44033276076217],[-3.032227717561338,51.44131415924758],[-3.032227715369496,51.44131415787439],[-3.0322276116329325,51.44131423487203],[-3.0322274340587225,51.441314362283485],[-3.0322274380051395,51.44131436420663],[-3.0308865104375333,51.44231052606716],[-3.030886505175975,51.44231052332003],[-3.030886268228536,51.442310705816844],[-3.0308862352601365,51.44231073020353],[-3.03088623613688,51.442310730752816],[-3.029573234658686,51.443321381005994],[-3.029573233343,51.44332138045635],[-3.0295731898180605,51.443321414983295],[-3.029573002097939,51.44332155966343],[-3.0295730064827993,51.44332156186132],[-3.0282883708971786,51.44434636523007],[-3.0282883682663666,51.444346363856454],[-3.028288260540282,51.4443464531908],[-3.0282881084150968,51.44434657430792],[-3.0282881119232297,51.44434657595656],[-3.0270322733982282,51.44538518892582],[-3.0270322716441296,51.44538518810148],[-3.027032211838973,51.44538523934637],[-3.027032013087121,51.44538540376288],[-3.0270320179110057,51.445385405961225],[-3.0258053220268466,51.446437541722645],[-3.0258053189570155,51.446437540348605],[-3.025805194934666,51.446437650788134],[-3.025805069601849,51.44643775820929],[-3.02580507267167,51.446437759583326],[-3.0246078919637265,51.447503101994165],[-3.0246078867007653,51.44750309979537],[-3.0246076931680186,51.447503278475615],[-3.0246076452279254,51.44750332122596],[-3.024607646543669,51.44750332177566],[-3.0234401064944714,51.44858176733212],[-3.0234401121959045,51.448581769805664],[-3.0223030219740603,51.44967257728345],[-3.0223030167109664,51.44967257508454],[-3.0223028372078504,51.44967275377299],[-3.0223027866160947,51.449672802554666],[-3.022302788370865,51.449672803104775],[-3.0211963129040886,51.45077580483035],[-3.021196311149898,51.45077580400593],[-3.0211962574668396,51.45077585991619],[-3.0211960867547076,51.450776029833],[-3.0211960915794824,51.45077603175728],[-3.0201205406376475,51.45189092082538],[-3.020120533180552,51.45189091807585],[-3.019075806499053,51.45301781867799],[-3.0190758130788393,51.45301782115252],[-3.018063063034488,51.45415549585722],[-3.018063059086302,51.454155494482194],[-3.018062949471055,51.45415562303],[-3.018062854394344,51.45415572964695],[-3.018062857465091,51.45415573074688],[-3.0170820175353,51.45530423544808],[-3.017082011393073,51.45530423352244],[-3.017081838797953,51.455304444301895],[-3.01708181546491,51.455304471710654],[-3.0170818163423716,51.455304471985734],[-3.01613315987592,51.45646348899208],[-3.0161331585600184,51.45646348844228],[-3.0161331259729027,51.456463529831616],[-3.0161329648165975,51.45646372690515],[-3.016132970519886,51.456463728830435],[-3.015216788611989,51.45763290451648],[-3.015216784662951,51.45763290341566],[-3.0152166868823835,51.457633033617405],[-3.0152165996834484,51.45763314517661],[-3.0152166031934335,51.457633146277026],[-3.0143331522785735,51.45881215851177],[-3.014333146574413,51.45881215686069],[-3.014333006922301,51.45881235175723],[-3.014332994588272,51.458812368477986],[-3.0143329954657885,51.45881236875311],[-3.0134823972896374,51.46000107135984],[-3.0134824047489643,51.46000107356119],[-3.0126652935882343,51.46119858268642],[-3.012665285689645,51.461198580484606],[-3.011881551328395,51.46240506019892],[-3.011881548695699,51.46240505937352],[-3.011881496235008,51.462405144081885],[-3.0118813904506982,51.46240530718895],[-3.011881395277651,51.46240530856501],[-3.011131601894855,51.46361989273817],[-3.0111315983838463,51.463619891911925],[-3.011131528711207,51.46362001061647],[-3.0111314471485717,51.46362014302454],[-3.0111314510980383,51.463620144125514],[-3.010415649083173,51.46484274430395],[-3.010415648205567,51.46484274402881],[-3.010415634090987,51.46484276925066],[-3.0104155207548415,51.46484296307044],[-3.0104155264602803,51.46484296444742],[-3.0097339562999914,51.46607317935915],[-3.0097339505944256,51.46607317798215],[-3.0097338486668646,51.46607337318374],[-3.0097338164655025,51.466073431029486],[-3.0097338182207483,51.46607343157977],[-3.00908670733114,51.467310851138265],[-3.009086705575856,51.467310850587964],[-3.0090866786509602,51.46731090514706],[-3.0090865736293906,51.46731110610531],[-3.0090865793350954,51.46731110748234],[-3.0084741098159253,51.468555386814764],[-3.0084741067430723,51.468555386263176],[-3.008474059495149,51.46855548853007],[-3.008473983129111,51.468555643708],[-3.0084739875187996,51.46855564453517],[-3.0078963468845914,51.46980640912325],[-3.0078963411779216,51.46980640802045],[-3.007896259451981,51.469806597205626],[-3.0078962280984958,51.46980666520066],[-3.007896230293052,51.46980666575142],[-3.007353581461437,51.471063569278186],[-3.0073535739985156,51.471063567899336],[-3.0068459089355155,51.472326675571615],[-3.006845916837839,51.472326676950935],[-3.0063737943209543,51.47359460045614],[-3.0063737916870132,51.47359459990493],[-3.006373762037989,51.47359468655345],[-3.00637369702724,51.473594860942],[-3.006373702295823,51.47359486177013],[-3.005937058453029,51.47486773751969],[-3.0059370505495893,51.47486773641461],[-3.0055359412416798,51.47614543785618],[-3.0055359359728206,51.47614543702801],[-3.005535884112956,51.47614561910954],[-3.005535858426601,51.476145700824425],[-3.005535861061388,51.47614570110137],[-3.005170564182327,51.4774273492017],[-3.0051705633045103,51.47742734892651],[-3.0051705553165413,51.477427380462686],[-3.005170499022287,51.47742757762591],[-3.005170504730554,51.47742757845454],[-3.0048410644694514,51.47871301120413],[-3.0048410635916127,51.478713010928935],[-3.0048410564838646,51.478713041643076],[-3.00484099657673,51.47871327583259],[-3.0048410031636976,51.47871327666213],[-3.0045475307533427,51.48000206766431],[-3.004547524604772,51.48000206710951],[-3.0045474818770104,51.480002281841074],[-3.0045474703248507,51.48000233230009],[-3.004547472081281,51.48000233257616],[-3.0042900500604923,51.48129412477085],[-3.0042900483040147,51.481294124494774],[-3.0042900398277492,51.481294174408276],[-3.0042899966539953,51.48129439078503],[-3.004290002802736,51.48129439133985],[-3.0040687125204832,51.482588790877706],[-3.004068708128096,51.48258879059895],[-3.0040686857551813,51.48258894719991],[-3.0040686670187586,51.48258905689974],[-3.004068670093216,51.482589057177165],[-3.003883577456901,51.48388570664224],[-3.0038835699898483,51.483885706086056],[-3.003734684029282,51.4851846728579],[-3.0037346932538838,51.48518467341587],[-3.0036221793675892,51.486484529258405],[-3.003622170142009,51.48648452897471],[-3.003545988185247,51.48778568018228],[-3.003545983792364,51.48778567990351],[-3.003545979465409,51.48778582253347],[-3.003545972108808,51.487785947605346],[-3.003545975622971,51.48778594788322],[-3.0035061824746667,51.489087485287314],[-3.0035061811572663,51.48908748501167],[-3.0035061810497123,51.48908752615609],[-3.0035061748036846,51.489087730500835],[-3.0035061805148104,51.48908773078095],[-3.003502777463188,51.490389510137085],[-3.003502772189851,51.490389510406004],[-3.0035027769698392,51.49038969885275],[-3.003502776781965,51.490389770718295],[-3.0035027789788895,51.49038977072053],[-3.0035357697866774,51.49169137400493],[-3.0035357662714968,51.49169137400135],[-3.003535772975664,51.49169149881316],[-3.003535776576089,51.49169163431939],[-3.0035357805306666,51.49169163432341],[-3.0036051721400736,51.49299293999664],[-3.003605180489581,51.49299293973084],[-3.003710927429282,51.49429303043217],[-3.0037109173211327,51.49429303097049],[-3.0038530324679837,51.49559203086931],[-3.003853029391223,51.495592031140475],[-3.00385304405599,51.49559213703382],[-3.0038530607927885,51.49559229093114],[-3.0038530651878537,51.49559229066131],[-3.0040314516239475,51.49688928529533],[-3.004031448107654,51.49688928556606],[-3.0040314688769567,51.4968894101177],[-3.004031487424029,51.496889544267454],[-3.0040314913797683,51.49688954399718],[-3.004246168989723,51.49818465637899],[-3.004246176461967,51.498184655837946],[-3.004496997293959,51.49947697303398],[-3.0044969946564124,51.49947697330561],[-3.004497016810585,51.49947707317183],[-3.004497047602699,51.499477231471914],[-3.004497051998133,51.499477231202064],[-3.0047839855848797,51.500766617995325],[-3.0047839772325404,51.5007666188098],[-3.0051070684500787,51.502053197042024],[-3.005107077682353,51.5020531959541],[-3.0054659723426864,51.5033355446659],[-3.0054659679461735,51.5033355452101],[-3.0054660167648266,51.503335703253576],[-3.0054660442020342,51.50333580120477],[-3.005466046839796,51.50333580093312],[-3.005860768202287,51.50461404380121],[-3.0058607594090354,51.50461404488962],[-3.006291275992253,51.50588804656829],[-3.0062912755520212,51.50588804684214],[-3.0062912790437717,51.50588805644597],[-3.006291361929535,51.505888301474734],[-3.006291368964181,51.505888300658846],[-3.0067573804464747,51.50715716350894],[-3.006757376048921,51.50715716432747],[-3.006757432390268,51.50715730591998],[-3.0067574734242637,51.507157417324784],[-3.0067574765024823,51.507157416779236],[-3.007258929755589,51.508421008664584],[-3.007258927556405,51.508421009211006],[-3.0072589559602245,51.50842107534433],[-3.0072590297737514,51.50842126166403],[-3.0072590350505326,51.508421260846355],[-3.0077957799642174,51.509679196329415],[-3.007795775566439,51.50967919714799],[-3.0077958450735522,51.5096793497247],[-3.007795887024685,51.50967944796379],[-3.007795890103064,51.50967944741822],[-3.008367880054581,51.51093159348589],[-3.085721608509103,51.49668609583448],[-3.0855349048089367,51.49627889330892],[-3.099412069106691,51.493880514166044],[-3.0993016149595984,51.49362286184591],[-3.099198411641373,51.49336411918239],[-3.1033989443967678,51.49273541999037],[-3.1033198947399545,51.492521416377336],[-3.1061314677049023,51.492132773526464],[-3.1060688393513565,51.49194863318684],[-3.107479822405184,51.491769632190866],[-3.1074271333646184,51.491600228534026],[-3.107379194322577,51.491430294992014],[-3.1102194068014843,51.49113394148261],[-3.110258632625791,51.49127295670288],[-3.1102586317469276,51.49127295697708],[-3.110258637454001,51.491272975081266],[-3.1102586400876904,51.49127298468193],[-3.110258640527158,51.49127298440767],[-3.1103017553563057,51.49141157898761],[-3.113123644532048,51.49105345787002],[-3.1131601933205877,51.491160871029074],[-3.1131601941994296,51.491160870754854],[-3.1131997328901964,51.4912678477448],[-3.113199732450747,51.49126784801906],[-3.113199735963953,51.491267857071136],[-3.113199740355306,51.49126786914054],[-3.113199740794703,51.49126786914057],[-3.1132422645765914,51.49137439898616],[-3.1132422641371917,51.491374398986125],[-3.1132422645764164,51.49137439980905],[-3.113242272920531,51.49137442038197],[-3.113242273359926,51.49137442038201],[-3.1132877690775618,51.49148047071193],[-3.1132877686381635,51.49148047071189],[-3.1132877743476306,51.491480483330015],[-3.1132877778609904,51.49148049183347],[-3.113287778300386,51.49148049183352],[-3.1133362319191646,51.49158603027622],[-3.1119556452727277,51.491841183984164],[-3.1119002618932794,51.491720544029654],[-3.110512741013016,51.491960600375215],[-3.110454246317554,51.491824229608504],[-3.1103995665602313,51.491687225594646],[-3.1089994654454003,51.49189688930221],[-3.108942983195729,51.49174403253301],[-3.1075372250632545,51.49193839294124],[-3.1075993628993595,51.492106570029144],[-3.1089994764210735,51.491896919750424],[-3.108999472908898,51.49189690987533],[-3.1090602117807173,51.49204910743645],[-3.109060211341311,51.4920491074364],[-3.1090602157321467,51.4920491178602],[-3.1090602240745873,51.49204913815912],[-3.1090602245140726,51.49204913788487],[-3.1091252182919344,51.492200671019525],[-3.1105127528693397,51.49196062780611],[-3.1105127471606546,51.49196061491357],[-3.110575055508766,51.49209634886145],[-3.1119556562518165,51.49184120839758],[-3.1120143927061235,51.49196120266955],[-3.112014392266726,51.4919612026695],[-3.1120143953411494,51.49196120843002],[-3.1120144041248095,51.491961226534414],[-3.1120144050036087,51.49196122653449],[-3.112076499387374,51.49208058663981],[-3.1120765007056463,51.49208058636564],[-3.1121419100010477,51.49219922808605],[-3.112141909561576,51.49219922836032],[-3.1121419179067806,51.4921992428988],[-3.112141923177396,51.492199252225355],[-3.1121419236168655,51.4921992519511],[-3.11221064037975,51.492317162937084],[-3.1122106399403435,51.49231716293704],[-3.1122106447719053,51.492317170892086],[-3.112210653995765,51.49231718625353],[-3.112210654435166,51.49231718625357],[-3.1122826580442013,51.49243433056464],[-3.1109429151297663,51.49276361560005],[-3.110861898618032,51.49263180181793],[-3.1108618981788503,51.492631800995],[-3.1107845795261557,51.49249912350557],[-3.109427232558057,51.49279900320228],[-3.109345462038649,51.49265066988583],[-3.109267851287709,51.492501467750486],[-3.107894556670061,51.492771576017084],[-3.1079799237456083,51.49293569929982],[-3.1066143688607606,51.493220713076234],[-3.1067125036321928,51.49339875136088],[-3.1094272492467123,51.49279903310252],[-3.109427243976579,51.49279902377582],[-3.109513138287765,51.492946423800966],[-3.1095131378483503,51.49294642380092],[-3.109513143997104,51.49294643395059],[-3.109513154976766,51.49294645287829],[-3.1095131558555855,51.49294645287841],[-3.109603170479386,51.493092914665496],[-3.110942930941457,51.492763642208416],[-3.111027604052169,51.49289452753746],[-3.111027603612699,51.4928945278117],[-3.11102761195824,51.4928945401559],[-3.111027620742841,51.49289455387161],[-3.111027621182254,51.49289455387166],[-3.1111159588244894,51.493024522809584],[-3.112436481084116,51.49266624617092],[-3.1125182083130887,51.492780877553955],[-3.112518207873617,51.49278087782821],[-3.1125182179769393,51.49278089126963],[-3.1125182245659277,51.492780900596294],[-3.1125182250053993,51.49278090032203],[-3.112603144722741,51.49289462373372],[-3.112603144283266,51.49289462400797],[-3.112603159658186,51.492894643484384],[-3.112603161854539,51.492894646501824],[-3.1126912486010583,51.49300742709737],[-3.1126912481615903,51.49300742737163],[-3.1126912587045963,51.493007439990194],[-3.1126912661723516,51.49300744986551],[-3.11269126661183,51.493007449591246],[-3.1127825116198546,51.493119275842375],[-3.1140597678442137,51.49270435224532],[-3.1141423168649203,51.49280129510783],[-3.114142315986054,51.49280129538206],[-3.114227580201735,51.492897343359026],[-3.1142275802016792,51.492897343633324],[-3.114227585473544,51.49289734911965],[-3.114227597335028,51.49289736256105],[-3.114227597774439,51.49289736256109],[-3.114315517010981,51.49299244761666],[-3.1143155165715126,51.49299244789092],[-3.114315529311815,51.492992461606676],[-3.1143155345837514,51.49299246681871],[-3.1144061171991835,51.49308659854793],[-3.115633877420612,51.49261705137067],[-3.1157137493285605,51.492696860959676],[-3.1157137524039302,51.49269686397712],[-3.115713765144701,51.49269687686984],[-3.11571376558416,51.49269687659557],[-3.1157958569232407,51.49277580357979],[-3.115795856044382,51.49277580385404],[-3.1158801589131495,51.492853840273796],[-3.1158801584737432,51.49285384027377],[-3.11588016726069,51.4928538482289],[-3.115880175608315,51.4928538556354],[-3.1159666302620117,51.492930946074516],[-3.1159666337768233,51.492930949091985],[-3.115966647396637,51.49293096143614],[-3.115966647836054,51.49293096143616],[-3.1160552446143606,51.49300709793473],[-3.116055244174955,51.493007097934694],[-3.1160552525227194,51.4930071047926],[-3.116055262188458,51.493007113296365],[-3.116055262627915,51.493007113022095],[-3.1161459751746885,51.49308227308146],[-3.1161459742958257,51.49308227335571],[-3.1162388118423863,51.49315646300609],[-3.11736573004531,51.492596996163066],[-3.1172883802025644,51.49253518440388],[-3.1172127689632725,51.492472537995006],[-3.117138910823304,51.49240906873527],[-3.117066863774262,51.49234482627854],[-3.1182535250688,51.491835785145604],[-3.1183111628570717,51.49188717743876],[-3.11831116285704,51.49188717771307],[-3.1183111698868253,51.49188718374786],[-3.1183111742804037,51.491887187862474],[-3.1183702403142335,51.49193794408994],[-3.118370239874819,51.49193794436422],[-3.118370249101453,51.491937952044886],[-3.118370252177002,51.491937954513666],[-3.1184307284447654,51.49198805986059],[-3.11843072800534,51.49198806013488],[-3.118430737232021,51.49198806754122],[-3.1184307403076046,51.491988069735726],[-3.118492607918611,51.492037508564316],[-3.1173657146675997,51.49259698381899],[-3.1174447807089787,51.4926579430948],[-3.1174447802695675,51.49265794309478],[-3.117444792132466,51.49265795214712],[-3.1174447965261396,51.49265795543889],[-3.117444796526169,51.49265795516459],[-3.117525538786201,51.49271803177907],[-3.1175255383467575,51.49271803205334],[-3.117607964735967,51.49277723203795],[-3.117607971765915,51.49277723697562],[-3.117607980992683,51.49277724383345],[-3.1176079814321223,51.49277724355918],[-3.11769205065144,51.49283553646163],[-3.1198152778190766,51.49162115055499],[-3.1198666796849874,51.4916555635184],[-3.1198666792455905,51.491655563518385],[-3.1198666875937713,51.49165556900453],[-3.11986668979066,51.49165557037609],[-3.1198666897906855,51.491655570101784],[-3.119919056250432,51.49168942460036],[-3.1199190566898225,51.49168942460037],[-3.1199723508357553,51.49172269649379],[-3.118963108599961,51.49236374471788],[-3.1190354119143064,51.492407335891706],[-3.119035411474874,51.49240733616599],[-3.1190354228988344,51.49240734274949],[-3.1190354259745092,51.492407344669665],[-3.1190354264139373,51.49240734439538],[-3.119108922920176,51.492450132972785],[-3.1191089334653976,51.492450139007644],[-3.1191089374198606,51.492450141202156],[-3.119183634147674,51.49249213074697],[-3.11918363458711,51.49249213047268],[-3.1192594783701737,51.49253329080836],[-3.119259477930728,51.492533291356935],[-3.1193364766769593,51.49257362741882],[-3.119336476237559,51.49257362741881],[-3.119336492055521,51.492573635648206],[-3.1193364924949667,51.49257363537391],[-3.1194145917190417,51.492613118631276],[-3.1194145912796305,51.492613118631276],[-3.1194145952341406,51.49261312055146],[-3.1194146070976556,51.49261312658635],[-3.1194146070976774,51.49261312631205],[-3.1194937988890445,51.492651752922335],[-3.119493798449611,51.49265175319662],[-3.119574073579368,51.492689518220025],[-3.11957407357935,51.492689518494316],[-3.1195740858823524,51.4926895239806],[-3.1195740898368816,51.49268952590081],[-3.119574090276288,51.492689525900815],[-3.119655392939693,51.49272640327537],[-3.118819197102104,51.49345811427541],[-3.1189221414482162,51.49350311538473],[-3.118922141887663,51.49350311511046],[-3.119026286581012,51.49354697043413],[-3.1190262861415663,51.49354697070841],[-3.119026307672206,51.49354697921234],[-3.1191316632540986,51.49358969313622],[-3.1191316628146875,51.493589693136215],[-3.1191316751179463,51.493589697799656],[-3.1191316839059486,51.49358970164009],[-3.1191316843453873,51.49358970136582],[-3.119238218295548,51.49363126099373],[-3.1192382178561036,51.493631261268014],[-3.1192382292806005,51.49363126538283],[-3.1192382393868536,51.4936312694976],[-3.119238239826289,51.49363126922331],[-3.1193459196239806,51.49367166193344],[-3.119345919184543,51.49367166220772],[-3.1193459367607206,51.493671668517074],[-3.1193459407153727,51.49367166988868],[-3.119345941154784,51.49367166988869],[-3.119454734278907,51.49371088305926],[-3.119454733839486,51.49371088305926],[-3.1194547355971225,51.49371088360789],[-3.11945475580974,51.49371089101451],[-3.1194547558097674,51.4937108907402],[-3.1195646284204717,51.493748912298116],[-3.119564627981036,51.49374891257241],[-3.1195646385268003,51.49374891613857],[-3.119564650830192,51.493748920253374],[-3.119564650830208,51.49374891997907],[-3.1196755699660454,51.49378573894853],[-3.119675569526603,51.49378573922282],[-3.1196755866635337,51.49378574443494],[-3.119675591936411,51.49378574635516],[-3.119675591936429,51.49378574608087],[-3.119787524196136,51.49382135121187],[-3.119787528590226,51.49382135258348],[-3.1197875461665627,51.493821358344185],[-3.1197875466059966,51.4938213580699],[-3.1199004572696993,51.49385573811251],[-3.119900457269681,51.493855738386806],[-3.1199004700125887,51.49385574195299],[-3.119900479679598,51.4938557449705],[-3.1200143349057994,51.49388888977209],[-3.1200143454516636,51.49388889278961],[-3.120014357315768,51.49388889608147],[-3.1201291447938093,51.49392080152455],[-3.120703338790713,51.493096546933366],[-3.120611499145728,51.49307101990662],[-3.1205204013831116,51.493044500419444],[-3.120430048583345,51.49301698902223],[-3.120340501388543,51.49298850464448],[-3.1210053876450523,51.4921912590557],[-3.121072545082886,51.49221262128188],[-3.121072545082876,51.49221262155618],[-3.121072556067812,51.49221262484792],[-3.121072558264793,51.492212625670845],[-3.121072558704212,51.49221262539654],[-3.121140303277544,51.49223325275355],[-3.1211403032775378,51.49223325302784],[-3.1211403107473026,51.492233255222345],[-3.12114031645948,51.492233256868204],[-3.121140316898879,51.49223325686822],[-3.121208627953539,51.4922531427716],[-3.121208627514122,51.4922531430459],[-3.1212086314687197,51.49225314414314],[-3.1212086415748903,51.492253146886256],[-3.1212774975777293,51.49227228530022],[-3.1207033198962937,51.49309654144707],[-3.120795848250795,51.493121060924366],[-3.120795847811356,51.493121061472955],[-3.120889066188593,51.4931445736735],[-3.120889066188607,51.493144573399206],[-3.120982890656667,51.49316705747341],[-3.1209828902172414,51.49316705774769],[-3.121077366469103,51.49318852494101],[-3.121077366908526,51.49318852466673],[-3.121172391238382,51.49320895083789],[-3.1211723907989546,51.49320895111217],[-3.1211724092541373,51.493208954952614],[-3.121172410132962,51.49320895495262],[-3.121267990444408,51.49322834256909],[-3.1212679900049887,51.49322834284337],[-3.121267993080855,51.49322834339202],[-3.121268009778416,51.49322834668383],[-3.121268010217843,51.49322834640954],[-3.1213641179434135,51.49324668998362],[-3.1213641179434046,51.49324669025792],[-3.1213641249739723,51.4932466913552],[-3.121364136838043,51.493246693824034],[-3.1213641372774594,51.49324669354974],[-3.1214607438495454,51.49326398731959],[-3.121460746486007,51.4932639878682],[-3.12146076318361,51.49326399088569],[-3.121460763183617,51.493263990611396],[-3.1215578391556007,51.49328022908946],[-3.1215578387161864,51.49328022908945],[-3.1215578457467656,51.49328023018672],[-3.121557858050272,51.493280232381245],[-3.121557858489692,51.49328023210695],[-3.121655373096548,51.493295410354385],[-3.121655373096541,51.49329541062868],[-3.1216553924306454,51.49329541364618],[-3.1216553928700685,51.49329541337188],[-3.1217533171042624,51.4933095269986],[-3.1217533171042566,51.4933095272729],[-3.1217533355595597,51.49330952974176],[-3.1217533368778008,51.49330952974178],[-3.1218516404134427,51.49332257408338],[-3.1218516404134364,51.49332257435767],[-3.121851647004622,51.49332257518061],[-3.1218516606264077,51.49332257682654],[-3.1218516606264153,51.49332257655224],[-3.121950314016274,51.49333454804168],[-3.1219503140162694,51.49333454831598],[-3.1219503175315726,51.49333454859031],[-3.12195033335043,51.493334550510525],[-3.121950333789848,51.493334550236234],[-3.1220493075865927,51.493345445306545],[-3.1220493071471718,51.493345445580836],[-3.122148590358715,51.4933552620368],[-3.122148590358707,51.49335526231109],[-3.1221485934346003,51.4933552625854],[-3.1221486101323057,51.4933552642313],[-3.122148610132311,51.49335526395701],[-3.122248132885075,51.493363995762834],[-3.1222481407945213,51.49336399631147],[-3.1222481526586856,51.493363997408736],[-3.1223479043997977,51.49337164346658],[-3.122184887761563,51.49426467901207],[-3.1223098520202917,51.494272878254826],[-3.1223098520202828,51.494272878803415],[-3.122435027703144,51.4942797154845],[-3.1224350272637222,51.4942797154845],[-3.1224350382492894,51.49427971603315],[-3.1224350523108146,51.494279716856106],[-3.122435052310818,51.4942797165818],[-3.1225603998586657,51.49428518850601],[-3.1225603998586715,51.494285187957416],[-3.122685856422848,51.49428929265539],[-3.122685856422846,51.494289292929686],[-3.1226858805911117,51.49428929347833],[-3.122685881469958,51.49428929347834],[-3.1228114334171266,51.494292030675375],[-3.122811445720972,51.494292030949694],[-3.1228114584642404,51.49429203122403],[-3.1228114584642417,51.49429203094972],[-3.1229370679927606,51.49429339982261],[-3.1229370802966074,51.49429339982262],[-3.122937093039877,51.49429340009694],[-3.1229370930398774,51.49429339982263],[-3.1230627205903874,51.494293399822624],[-3.123062720590388,51.49429340009693],[-3.1230627460769274,51.494293399822624],[-3.1230627465163505,51.49429339982261],[-3.123188355166024,51.49429203122402],[-3.123188368348716,51.494292030949694],[-3.123188380213139,51.49429203094967],[-3.123313932160308,51.49428929375263],[-3.123313957207419,51.49428929320397],[-3.1233139572074173,51.49428929292968],[-3.123439413771596,51.494285188231714],[-3.123439413771599,51.49428518850601],[-3.1235647613194497,51.4942797168561],[-3.1235647617588724,51.49427971685609],[-3.123564786366545,51.49427971575879],[-3.123564786366543,51.4942797154845],[-3.1236899365628963,51.494272880175046],[-3.1236899506244167,51.49427287935207],[-3.1236899616099807,51.49427287880341],[-3.1238149267475483,51.49426467901206],[-3.1238149267475417,51.49426467873777],[-3.123939618051977,51.49425512104895],[-3.1239396426596056,51.49425511885439],[-3.124064048255561,51.494244203817665],[-3.1240640482555646,51.49424420409195],[-3.124188179117702,51.49423193061094],[-3.124188179117686,51.49423193006235],[-3.1243118985751894,51.494218311579935],[-3.1243119139549242,51.494218309933984],[-3.1243119231827636,51.49421830883671],[-3.1244352422105686,51.494203343982576],[-3.12443524221058,51.49420334425688],[-3.124435262423925,51.494203341787966],[-3.1244352676969624,51.49420334096501],[-3.1245581493732995,51.49418703522682],[-3.1245581542069156,51.494187034403865],[-3.1245581739808257,51.49418703193494],[-3.124558173980816,51.49418703166064],[-3.1246805818234744,51.49416938942901],[-3.1246805980819934,51.49416938696011],[-3.1246806059915526,51.49416938586281],[-3.1248025017608683,51.49415041262574],[-3.1248025087915705,51.494150411528445],[-3.1248025259289123,51.494150408785224],[-3.124923872703711,51.494130110305115],[-3.1249238907198595,51.49413010701327],[-3.124923896871712,51.49413010591597],[-3.125044681459607,51.494108484114825],[-3.1250446814595976,51.49410848384052],[-3.1251648181645324,51.49408555408224],[-3.125164818603962,51.49408555435652],[-3.125164838817144,51.494085550241714],[-3.125164843211316,51.494085549418756],[-3.125284320159695,51.49406131417419],[-3.1252843205991225,51.49406131444847],[-3.1252843421305134,51.49406130978502],[-3.1252843443275995,51.49406130951066],[-3.1254031500854,51.494035770976446],[-3.1249224656162977,51.493188520826266],[-3.1250169040790645,51.49316706213676],[-3.1250169045184917,51.493167062411054],[-3.125016923413015,51.49316705774769],[-3.1251107474416586,51.4931445736735],[-3.1251107513963237,51.49314457285053],[-3.125110765896763,51.493144569284425],[-3.1252039473638193,51.493121066136325],[-3.125203958788388,51.49312106311885],[-3.1252039658188946,51.49312106147294],[-3.125296474839534,51.49309654693335],[-3.1252964801124046,51.49309654556178],[-3.125296493733985,51.493096541995655],[-3.1253883043782165,51.49307102292408],[-3.1259854567539147,51.49388889608145],[-3.1260993339506604,51.49385574497049],[-3.1260993396629915,51.49385574332456],[-3.126099357239397,51.49385573811247],[-3.1260993567999793,51.49385573811248],[-3.126212266145455,51.49382135861849],[-3.1262122665848753,51.49382135861848],[-3.126212284600628,51.493821352857765],[-3.1262122894341386,51.493821351486154],[-3.12632424322484,51.49378573949713],[-3.126324243224818,51.49378573922282],[-3.12643516280007,51.49374892025336],[-3.1264351693911694,51.49374891805879],[-3.126435185649223,51.493748912572386],[-3.126435185209803,51.49374891257241],[-3.126545058259889,51.493710890465884],[-3.1265450586993246,51.49371089074016],[-3.1266538948851035,51.493671661933384],[-3.126653894885083,51.493671661659086],[-3.12676157468281,51.49363126922327],[-3.126761588743691,51.493631263736894],[-3.1267615953347456,51.493631261268014],[-3.1268681297242726,51.49358970136578],[-3.126868129724306,51.49358970164008],[-3.1269735059580706,51.4935469794866],[-3.1269735235340668,51.49354697208002],[-3.12697352748868,51.493546970708394],[-3.1270776726214593,51.49350311538472],[-3.12789330491083,51.49424382103231],[-3.1280168132154134,51.49418982992075],[-3.128016819806408,51.494189826903195],[-3.1280168387006673,51.49418981867351],[-3.1280168382612046,51.49418981839923],[-3.128138794966576,51.49413450072832],[-3.1281387954060293,51.49413450100259],[-3.1282592343407507,51.494077840043545],[-3.1282592339012707,51.49407783949497],[-3.1283780210446026,51.494019898895324],[-3.1283780214840595,51.4940198991696],[-3.128495195938463,51.493960660280344],[-3.128495206483879,51.493960654793916],[-3.128495218347492,51.49396064875885],[-3.1286107181547873,51.493900142034605],[-3.1286107177153286,51.49390014176032],[-3.128724484873305,51.49383839875293],[-3.128724485312761,51.49383839902721],[-3.128836553212551,51.49377540081356],[-3.1288365523336483,51.493775400265015],[-3.1289467961876247,51.493711217076104],[-3.1289467966270723,51.49371121735037],[-3.128946815520532,51.49371120582898],[-3.1289468190356295,51.49371120390873],[-3.1290552735541133,51.49364581572424],[-3.129055273114663,51.493645815449966],[-3.129161862284112,51.4935792689092],[-3.1291618745866976,51.49357926095396],[-3.1291618842530395,51.493579254918934],[-3.1292665962101096,51.49351155661065],[-3.1292666010432226,51.49351155331885],[-3.1292666159820346,51.49351154371767],[-3.1292666155425803,51.4935115434434],[-3.12936941909222,51.49344271476941],[-3.129369425243457,51.49344271065463],[-3.1293694401821517,51.49344270050486],[-3.1293694397426934,51.49344270023058],[-3.129470302372235,51.493372762318465],[-3.1294703028116775,51.49337276259274],[-3.129569233749326,51.493301707766676],[-3.1295692333098706,51.49330170749239],[-3.129666121836984,51.49322961558372],[-3.129666123594429,51.49322961421214],[-3.1296661420477685,51.49322960049627],[-3.129666141608322,51.493229600221994],[-3.1297610013483665,51.493156462457485],[-3.130887947579347,51.493715918419404],[-3.130996234366042,51.49362938075574],[-3.130996234805503,51.49362938103001],[-3.131102087489884,51.493541675205286],[-3.1311020989129523,51.493541665329836],[-3.1311021072606358,51.49354165847185],[-3.131205469611728,51.493452830852604],[-3.131205477519911,51.49345282372035],[-3.1312054907003444,51.49345281247327],[-3.131205490260874,51.49345281219901],[-3.131306372390705,51.49336285511075],[-3.1313063719512346,51.49336285483649],[-3.1314047035722283,51.493271829733004],[-3.1314047040116857,51.49327183000727],[-3.1314047171917214,51.4932718171144],[-3.131404722463823,51.493271812450985],[-3.1315004943584754,51.49317972948825],[-3.131500496115763,51.49317972756805],[-3.131500513689152,51.493179710834696],[-3.1315005132496814,51.49317971056043],[-3.131593714004126,51.49308658044305],[-3.1303659515864024,51.49261703600908],[-3.130443579467478,51.49253636850802],[-3.1304435803463275,51.49253636878226],[-3.1305189571788485,51.49245485078004],[-3.1305189668440017,51.49245484008187],[-3.130518971237245,51.492454835144265],[-3.1305920566106766,51.49237250834083],[-3.1305920557318214,51.492372508066595],[-3.1306628276889956,51.4922893990744],[-3.130662827249544,51.49228939880014],[-3.1307312515327386,51.49220554739855],[-3.130731262076138,51.49220553395734],[-3.1307312642726917,51.49220553121423],[-3.1307973334232444,51.4921209470082],[-3.1307973448450386,51.492120931372554],[-3.1307973466023182,51.49212092945236],[-3.1308610408615034,51.49203563823104],[-3.130861050086764,51.49203562561277],[-3.1308610527225085,51.492035621772445],[-3.1309223523321044,51.49194964932483],[-3.1309223567249687,51.49194964274142],[-3.1309223646322852,51.49194963176903],[-3.1309812502743077,51.49186300580411],[-3.132311543730522,51.49220687045132],[-3.132242839775174,51.492307944768115],[-3.1321713104306887,51.49240826851472],[-3.1321713042806723,51.4924082767441],[-3.1320969815995157,51.49250780437972],[-3.1333966517758967,51.492894647324675],[-3.133481589503662,51.49278090032191],[-3.133481592139237,51.49278089648154],[-3.1334816057567476,51.492780878376756],[-3.133481605317271,51.49278087810249],[-3.133563332546255,51.49266624671945],[-3.1348838548059073,51.493024523358116],[-3.1349722100176947,51.49289452836024],[-3.134972209138806,51.49289452808603],[-3.1350568822493203,51.49276364193411],[-3.1350568831281995,51.49276364220832],[-3.1351378996400263,51.49263182842621],[-3.1351379075457126,51.49263181471059],[-3.135137915890873,51.492631801269205],[-3.1352152336649732,51.49249912460273],[-3.1338579036294525,51.4921992291832],[-3.133923313364192,51.49208058718843],[-3.1339233181954462,51.49208057813622],[-3.1339233265404123,51.49208056277484],[-3.133985407748085,51.4919612276317],[-3.1339854086269563,51.49196122790592],[-3.134044168357838,51.491841185355604],[-3.134099540758054,51.49172056899161],[-3.134099547784564,51.49172055308175],[-3.1340995517369525,51.491720544029604],[-3.134151542980056,51.49159934288426],[-3.1341515434193203,51.49159934233562],[-3.134151553080436,51.491599319293805],[-3.134200147382088,51.49147757012879],[-3.1342001482608834,51.49147757012871],[-3.1342453412554825,51.49135528474338],[-3.1342453456462636,51.49135527185103],[-3.1342453495981863,51.491355261153096],[-3.1342871079391634,51.49123252732941],[-3.135698057834607,51.4914115792619],[-3.1357411731034484,51.4912729857791],[-3.1357411783710587,51.49127296740066],[-3.135741181443986,51.49127295725137],[-3.135780407707607,51.491133941756736],[-3.1357804120967785,51.49113392474989],[-3.135780414730449,51.49113391514922],[-3.1358157480731146,51.49099449328197],[-3.1358157502673807,51.490994483681355],[-3.135815755095302,51.49099446448007],[-3.1358471845789744,51.490854679035124],[-3.1358471867725313,51.490854666965845],[-3.135847190283488,51.490854652153445],[-3.135874706727977,51.49071454537658],[-3.135874707606751,51.49071454537649],[-3.135898307976346,51.49057413345427],[-3.135898307976192,51.490574132905664],[-3.135898312801481,51.4905741046526],[-3.135898311922706,51.4905741046527],[-3.1359179791441574,51.49043348386739],[-3.135917980462315,51.49043348386724],[-3.1359337193935755,51.49029261417301],[-3.134496614648247,51.490237894126295],[-3.134507101521279,51.49011259310383],[-3.1345071024000446,51.49011259310376],[-3.1345140873133808,51.48998717310252],[-3.134514087747719,51.48998715307884],[-3.1345140881856497,51.48998714731857],[-3.134517563720224,51.48986169419472],[-3.1345175645989847,51.489861694194644],[-3.134517532978096,51.489736198073594],[-3.1345175334174753,51.48973619807355],[-3.1345175329741566,51.48973618243869],[-3.1345175329715995,51.489736172289724],[-3.1345139933676984,51.48961072176932],[-3.1345139938070763,51.48961072176929],[-3.134513992924935,51.48961070832884],[-3.134513992482447,51.48961069598555],[-3.134513992043069,51.48961069598558],[-3.1345069449208904,51.489485277076355],[-3.1345069444815135,51.4894852770764],[-3.1344963920825855,51.48935995259138],[-3.133059343409227,51.48941495765938],[-3.133068575135944,51.48952463788535],[-3.133074737420193,51.489634364179764],[-3.1330778311216694,51.48974415025781],[-3.133077855335161,51.48985397637049],[-3.13307481002083,51.48996376214893],[-3.1330686951553592,51.49007350210695],[-3.133059512031005,51.49018317896331],[-3.1330472689537143,51.490292700278665],[-3.131611917857042,51.4902222958501],[-3.1315987922066824,51.49031607986782],[-3.1315830516827976,51.490409677826634],[-3.1315646931969963,51.490503106732135],[-3.1315437268299586,51.49059631638609],[-3.1315437263908277,51.490596317757614],[-3.1315201595903748,51.490689275242545],[-3.1314939984884003,51.49078195861273],[-3.131465244386696,51.49087436210613],[-3.131433917030876,51.49096642180834],[-3.1314339161525964,51.490966424551374],[-3.131400017284376,51.491058134974324],[-3.1313635525980477,51.49114947910901],[-3.1313245528229374,51.49124038151937],[-3.131283006086561,51.49133087073052],[-3.131238944437628,51.49142087377473],[-3.1311923590783626,51.491510410673406],[-3.131143297434188,51.49159939117677],[-3.1297860212754314,51.49129944859065],[-3.1297860203968466,51.49129944996218],[-3.1298269164916346,51.491225283326585],[-3.129826915612803,51.491225283052344],[-3.129865723517352,51.49115070041615],[-3.1299024476329027,51.49107568971208],[-3.129902448950626,51.49107568669475],[-3.1299024546609804,51.49107567517396],[-3.1299370686415604,51.491000290990534],[-3.1299695764501574,51.490924525923354],[-3.129969577767865,51.49092452290601],[-3.1299695825994065,51.49092451138528],[-3.129999960966176,51.49084841892526],[-3.1299999622838905,51.49084841590793],[-3.1299999671152476,51.49084840329001],[-3.1300282134151196,51.490771991667856],[-3.1300282147327425,51.490771988101926],[-3.130028218685557,51.49077197712983],[-3.1300543245837638,51.490695269114184],[-3.1300543250228974,51.490695267468375],[-3.1300543298540147,51.49069525347897],[-3.130078286576924,51.49061827403274],[-3.1300782878944537,51.49061826991822],[-3.130078291407728,51.490618258123256],[-3.130100092817649,51.49054102836896],[-3.130100095013351,51.490541020414234],[-3.130100096769832,51.490541013556715],[-3.1301197358509514,51.49046355790835],[-3.130119736289912,51.49046355516535],[-3.1301197398029514,51.490463541998906],[-3.130137209978872,51.49038588404744],[-3.1301372117350006,51.490385874995546],[-3.1301372130522593,51.490385869235254],[-3.1301525103830774,51.490308032845675],[-3.1301656339998414,51.49023001088719],[-3.130165633121072,51.49023001088724],[-3.130176569866646,51.49015188866756],[-3.1301765711828136,51.49015187604984],[-3.13017657162181,51.49015187358115],[-3.1301853219456306,51.490073644243445],[-3.130185321945331,51.490073642323374],[-3.1301853237006174,51.49007362805984],[-3.1301918854206985,51.489995315468526],[-3.130191886297305,51.48999530175366],[-3.1301918862970894,51.48999530038217],[-3.1301962581101734,51.48991692785292],[-3.1301962589864307,51.48991691194365],[-3.1301984391500466,51.48983850361487],[-3.1301984272375165,51.489760068538466],[-3.1301984267956313,51.4897600526293],[-3.130196222387541,51.48968164593893],[-3.1301962219469965,51.48968163853296],[-3.1301962215062358,51.48968162975549],[-3.130191825054279,51.48960325803412],[-3.1301918250540637,51.489603256662626],[-3.1301918241731497,51.48960324294786],[-3.130191823733772,51.489603242947894],[-3.130185237449985,51.48952493088181],[-3.13018523788936,51.48952493088178],[-3.1301764617862124,51.48944668642522],[-3.1301764600270285,51.48944667572776],[-3.1301764595869623,51.489446671339046],[-3.1301654993963273,51.489368550447665],[-3.1301654980772975,51.48936854468752],[-3.1301654967575785,51.48936853453862],[-3.1301523516107004,51.48929053008015],[-3.130152351171327,51.48929053008017],[-3.1301370303144886,51.48921269554114],[-3.130137029434972,51.48921269060384],[-3.1301370272365148,51.48921268045502],[-3.130119532878957,51.48913502488621],[-3.1301195333183296,51.489135024886195],[-3.1301195298012927,51.48913501172016],[-3.1301195293614934,51.48913500897723],[-3.130099862833521,51.48905754088082],[-3.130099862394147,51.48905754088083],[-3.1300780333785623,51.488980297833756],[-3.1300780329391893,51.488980297833784],[-3.130054053315873,51.48890332015572],[-3.1300540506785017,51.488903312749876],[-3.1300540484804995,51.488903305344],[-3.130027920018579,51.48882660071385],[-3.1300279156231006,51.48882658919365],[-3.130027914304354,51.48882658507929],[-3.1299996462446416,51.48875017763315],[-3.1299996440470346,51.488750172695944],[-3.12999964009107,51.48875016199862],[-3.129969239476992,51.48867407367826],[-3.1299692399163175,51.48867407340394],[-3.129936711152892,51.48859831298496],[-3.129936704559993,51.48859829735045],[-3.129902068754708,51.488522917494784],[-3.1299020691940354,51.48852291722046],[-3.129902062601454,51.488522903506016],[-3.129902062162,51.48852290295745],[-3.1298653254767976,51.488447911891775],[-3.1298653259161284,51.488447911617456],[-3.1298653193237267,51.4884478990002],[-3.129865318005217,51.48844789625731],[-3.1298264909978535,51.48837331784284],[-3.12982648836104,51.48837331372855],[-3.1298264826475988,51.48837330248273],[-3.12978557631449,51.488299157563326],[-3.129785571480203,51.4882991490604],[-3.1297855679644284,51.48829914330038],[-3.1297425862700097,51.48822544010256],[-3.128394087703745,51.48854028298485],[-3.1284284761160954,51.48859925432988],[-3.128461207799888,51.48865858534415],[-3.128461208239325,51.48865858589273],[-3.128492269125961,51.48871825079438],[-3.128521664483246,51.48877825973346],[-3.12854937672606,51.48883857650491],[-3.128575398816722,51.48889918766971],[-3.128599721079747,51.48896006799416],[-3.1286223373558286,51.489021203490466],[-3.1286432419246917,51.48908257825071],[-3.1272324251823336,51.4892618339076],[-3.1272167506367574,51.489215810810876],[-3.127199788772866,51.489169956450155],[-3.1271997878939213,51.489169954255814],[-3.1271997852570195,51.489169947124196],[-3.127181547944522,51.48912429359118],[-3.127181544428832,51.489124285911004],[-3.1271815439893564,51.48912428481384],[-3.1271620329897525,51.48907883677092],[-3.127162032989699,51.489078836222326],[-3.12716202903454,51.48907882744497],[-3.127141250503843,51.48903359888043],[-3.127141247427613,51.48903359202313],[-3.1271412465486947,51.489033590103084],[-3.12711920708221,51.488988595005125],[-3.127095908441221,51.488943838036036],[-3.1270959036073234,51.488943828984425],[-3.1270713620546964,51.488899341412704],[-3.1270713594180553,51.488899336749746],[-3.127071357220857,51.488899332909675],[-3.12704557012318,51.48885511034588],[-3.1256970344074917,51.489169922185226],[-3.1256790262719365,51.489140638154524],[-3.125679024953702,51.48914063623448],[-3.1256790227566134,51.489140632394374],[-3.125660198300239,51.48911154916432],[-3.1256601987395998,51.489111548890016],[-3.125640556645429,51.489082663442986],[-3.1256405562060783,51.48908266371729],[-3.1256201180064886,51.48905400787079],[-3.1255988731569864,51.48902556818411],[-3.1255768453865844,51.488997375377515],[-3.1255768431895414,51.488997372086004],[-3.1255768409925597,51.48899736989169],[-3.125554023711321,51.488969415735966],[-3.1255304331781795,51.48894171997971],[-3.125530429223568,51.48894171504245],[-3.125530428784177,51.48894171476817],[-3.1255060623637485,51.48891427439365],[-3.1255060623637556,51.48891427466794],[-3.125480938511713,51.48888710997226],[-3.1254809367541276,51.48888710805222],[-3.125480933238964,51.48888710448645],[-3.1254550537137926,51.48886021738915],[-3.1254550528350027,51.48886021656629],[-3.1254550484410695,51.48886021217762],[-3.1254284211527517,51.488833611181256],[-3.127856832423291,51.487867672623814],[-3.1278020859875317,51.48781504943602],[-3.1278020842299172,51.48781504779032],[-3.1278020750024482,51.48781503928749],[-3.1277458771101228,51.487763031379615],[-3.1277458749130993,51.48776302918533],[-3.127745865685643,51.487763020682515],[-3.1277458652462857,51.48776302068253],[-3.1276882229304284,51.48771163381245],[-3.127688222930399,51.48771163353815],[-3.1276882163394415,51.48771162805248],[-3.127688211066613,51.487711623115345],[-3.1276291401480707,51.487660870995164],[-3.1276291401480534,51.487660870720866],[-3.1275686485383054,51.48761076075411],[-3.127568648538273,51.487610760479825],[-3.127568637992805,51.48761075197704],[-3.127568635795819,51.487610750057044],[-3.127506763921304,51.48756131597849],[-3.127506764360665,51.48756131597846],[-3.1275067538152816,51.48756130802427],[-3.127506752497067,51.487561306652836],[-3.1274435082685192,51.48751255422023],[-3.127443508268492,51.487512553945926],[-3.1274434964049758,51.48751254516888],[-3.1274434950867995,51.487512544071755],[-3.1273788855360247,51.48746447876856],[-3.1262841826853824,51.48804824917819],[-3.1262347351312085,51.48801274012005],[-3.12623472502537,51.488012732988615],[-3.1261842824296493,51.48797776128339],[-3.126184281990278,51.4879777612834],[-3.1261328799434915,51.487943351066974],[-3.126080513612734,51.48790949959525],[-3.1271073257921094,51.487279481578575],[-3.127036238078481,51.48723510532633],[-3.125018129493359,51.48851731967881],[-3.124981984853325,51.488495530452745],[-3.1249819795807676,51.488495527161284],[-3.1249819773838805,51.48849552606414],[-3.124945229983457,51.488474134018226],[-3.124945225589688,51.488474131549616],[-3.1249452225140306,51.488474129629616],[-3.1249078820194587,51.488453141071965],[-3.1249078771863057,51.48845313832908],[-3.1249078749894212,51.48845313695762],[-3.124869945794269,51.48843255408199],[-3.1248699457942823,51.48843255435628],[-3.1248314551396676,51.48841239197367],[-3.124831453382164,51.48841239115081],[-3.1248314472309078,51.48841238785934],[-3.1247923990708846,51.48839264816352],[-3.1247923916015248,51.48839264459778],[-3.124792391162138,51.488392644049185],[-3.124752797359482,51.488373333073945],[-3.124752797359467,51.488373332799654],[-3.1247126535200094,51.48835444835013],[-3.1247126530806315,51.488354448350144],[-3.124672003141252,51.488336010997415],[-3.1246719969900405,51.488336008528826],[-3.124671995671924,51.48833600770596],[-3.1246308308446022,51.48831801388375],[-3.1246308361170603,51.48831801607804],[-3.126261660150284,51.48683650980517],[-3.126178334273517,51.48680142406999],[-3.1261783347128502,51.48680142379569],[-3.126178323289417,51.48680141940725],[-3.1261783171383084,51.48680141666445],[-3.1260940235615746,51.486767244028805],[-3.1260940235615835,51.48676724430311],[-3.1260088049000885,51.48673400177026],[-3.1260088053394437,51.48673400177025],[-3.126008790401151,51.4867339962847],[-3.1260087873256133,51.4867339949133],[-3.125922651045324,51.486701685498296],[-3.1259226448942874,51.48670168330408],[-3.1259226352283567,51.486701679738474],[-3.125835608566256,51.48667031331333],[-3.125835601097157,51.48667031084485],[-3.1258355905525224,51.48667030700496],[-3.1257477025027915,51.486639894538904],[-3.1257476981092154,51.48663989316752],[-3.1257476844891015,51.486639888504804],[-3.1256589600913345,51.486610438224254],[-3.125658960091321,51.486610437949956],[-3.125658943835097,51.486610432738686],[-3.1256589416383167,51.48661043219013],[-3.1255693892363365,51.48658194711028],[-3.1255693887970026,51.486581947384586],[-3.125479070335898,51.48655444725159],[-3.125479065502993,51.4865544458802],[-3.1254790514436106,51.48655444176605],[-3.1253879800996063,51.48652793069234],[-3.125387973509283,51.486527928772404],[-3.1253879616466853,51.4865279252068],[-3.1252961628977647,51.48650241059612],[-3.1252961628977576,51.48650241032182],[-3.1252961444448966,51.48650240565914],[-3.1252961440055316,51.486502405384854],[-3.1252036464048767,51.48647789464101],[-3.1252036464048616,51.48647789436671],[-3.125110456098437,51.48645438940804],[-3.1251104394030347,51.48645438529389],[-3.125016625803489,51.48643190422109],[-3.12501661701645,51.48643190230117],[-3.1250166064719873,51.48643189955839],[-3.1249221809970664,51.4864104448385],[-3.125402705120669,51.485563175856555],[-3.125283919269114,51.48553764203257],[-3.125283905649471,51.48553763928985],[-3.12528389510523,51.48553763709569],[-3.1251644381385746,51.48551340662143],[-3.1251644337451476,51.48551340579862],[-3.125164413535371,51.485513401684535],[-3.1251644135353858,51.48551340195883],[-3.1250442946706314,51.48549047592932],[-3.1250442951099617,51.485490475655006],[-3.124923507308377,51.48546885379405],[-3.124538816277276,51.48633498573066],[-3.1246354349262413,51.48635228129473],[-3.1247315616195745,51.486370628165396],[-3.1248271313394116,51.48639001345299],[-3.124827132218109,51.48639001345296],[-3.1249221616656,51.48641044045008],[-3.124441624563087,51.48725770816285],[-3.1243703654788995,51.48724239064956],[-3.124370350980082,51.48724238763247],[-3.1242986596402047,51.48722784579139],[-3.1242265861281298,51.48721408977019],[-3.1242265738261272,51.48721408757594],[-3.1242265729474177,51.487214087575964],[-3.12415412560757,51.48720111874533],[-3.1240813141023547,51.48718893902461],[-3.1240813114662194,51.48718893875034],[-3.124081299164236,51.48718893655607],[-3.1240081726981024,51.48717755444733],[-3.1240081612748445,51.48717755280165],[-3.1240081577599934,51.4871775522531],[-3.123934709739002,51.48716696638431],[-3.123860976626065,51.48715718251485],[-3.1237869948839765,51.487148205032696],[-3.1237869874149347,51.48714820420985],[-3.123786979506541,51.487148203387015],[-3.123712744737983,51.487140031743124],[-3.1237127447379884,51.487140032017415],[-3.123638307904341,51.48713267197127],[-3.1238510917158155,51.48624372821176],[-3.1237515652661965,51.486234996109665],[-3.1237515652661916,51.486234995835375],[-3.12365180689562,51.486227349316195],[-3.1238147662176403,51.485334310775734],[-3.123689809867364,51.48532611202657],[-3.123689826122843,51.48532611284935],[-3.123827793782886,51.484431434551276],[-3.1236775855536876,51.484423230477425],[-3.1235646501657874,51.48531927585117],[-3.1236898002019404,51.48532611120374],[-3.1235518324135545,51.4862207894765],[-3.1236517893217774,51.48622734794482],[-3.123488828026295,51.48712038639634],[-3.1235636514275487,51.48712612160246],[-3.1236382925269237,51.48713267059986],[-3.1234255057120985,51.48802161411205],[-3.1234751515645147,51.488026522732596],[-3.1235246322333206,51.488031969502686],[-3.1235739644160057,51.48803795579397],[-3.123286941145941,51.48891872796341],[-3.12326227964929,51.488915735446184],[-3.123237533797961,51.488913011460234],[-3.1232375329192195,51.488913011460234],[-3.123237528525501,51.48891301091165],[-3.123212715454565,51.48891055737696],[-3.123187832527362,51.488908374293494],[-3.1231878294517603,51.4889083740192],[-3.123187827254904,51.4889083737449],[-3.123162891606485,51.488906462484096],[-3.123137901478932,51.48890482277157],[-3.123112869613573,51.488903455430226],[-3.1231128643411155,51.48890345515593],[-3.1230878030399047,51.488902361282875],[-3.1230627101055357,51.48890154005523],[-3.1230375969612156,51.488900992570144],[-3.123012468000207,51.4889007188276],[-3.122987340357538,51.4889007188276],[-3.1229622219414397,51.48890099257014],[-3.1229371087971196,51.48890154005523],[-3.122937105282149,51.488901540329536],[-3.122937104403406,51.48890154032952],[-3.1229120158627515,51.48890236100859],[-3.1228869492890827,51.48890345515593],[-3.122861912151267,51.488904822771566],[-3.1228369272961722,51.4889064622098],[-3.122811986375298,51.488908373744906],[-3.122787098175635,51.488910557376954],[-3.1227622851046974,51.48891301091165],[-3.1227622807109805,51.48891301146024],[-3.122762279832238,51.488913011460234],[-3.122737539253369,51.4889157348976],[-3.122712868090543,51.48891872851199],[-3.1226882940237584,51.48892198956056],[-3.122663805189562,51.4889255188662],[-3.1226394239955177,51.488929313960384],[-3.1226151574711793,51.48893337319741],[-3.122591003419307,51.488937696851664],[-3.122566972823808,51.48894228327744],[-3.122566970626945,51.488942283826034],[-3.1225669688694584,51.48894228410032],[-3.1225430700780348,51.48894713192626],[-3.1225193162715197,51.48895223786096],[-3.122519311438412,51.48895223895811],[-3.122495704373926,51.488957603001644],[-3.119470877934815,51.483906176615776],[-3.119636129508067,51.483868628600746],[-3.119802427589516,51.483832881962286],[-3.119969709372331,51.483798950957734],[-3.1201378830548534,51.48376685340999],[-3.1203069332790268,51.48373659205785],[-3.1204768503982683,51.48370816936601],[-3.120647465730734,51.48368161303135],[-3.12064746748803,51.483681612757074],[-3.120818862328346,51.48365691043469],[-3.1209909031417666,51.483634081593976],[-3.1211635104310655,51.48361313637985],[-3.121336731224288,51.48359406875598],[-3.121510452636328,51.48357689161018],[-3.1216846087899732,51.483561611796894],[-3.121859162364232,51.48354823178256],[-3.122034085263985,51.48353675458239],[-3.122209261090978,51.48352718705141],[-3.1223847236946356,51.48351952726859],[-3.1225603729318836,51.48351378044391],[-3.122736128868547,51.483509948770745],[-3.122911963410241,51.483508032797175],[-3.12308786120288,51.48350803279717],[-3.123263694426617,51.483509949045015],[-3.1234394411375686,51.48351378016962],[-3.1234394406982515,51.48351378044391],[-3.123615089935499,51.483519527268584],[-3.123790552539152,51.48352718677711],[-3.123790552978472,51.483527186777096],[-3.1239657560433214,51.48353675595363],[-3.1241037129045393,51.48264207733074],[-3.124303605548892,51.48265519324964],[-3.136505999660156,51.415676193711256]],[[-3.130203952752984,51.49277580769445],[-3.130119654717096,51.49285384027377],[-3.128932964354688,51.49234481366014],[-3.1290032029530668,51.49227979718667],[-3.129003214815485,51.49227978593991],[-3.1290032161335106,51.49227978456836],[-3.129071626954106,51.49221401273454],[-3.1302860476068113,51.49269687741845],[-3.130203952752984,51.49277580769445]],[[-3.1297430510375497,51.491373169050036],[-3.129698042669619,51.49144638462949],[-3.1296980391553086,51.49144639038993],[-3.1296509759555664,51.49151912577472],[-3.1296509763949913,51.49151912604899],[-3.129601902285207,51.491591314580205],[-3.1295508080324006,51.491662971342144],[-3.128240585146455,51.491290306251685],[-3.1282814540515664,51.49123299237885],[-3.128281455369521,51.49123299045871],[-3.1282814623986734,51.49123298058372],[-3.1283207220928877,51.49117523091209],[-3.1283207278039473,51.491175221860054],[-3.128320730000554,51.49117521884269],[-3.128358369505896,51.49111704928356],[-3.1283583699453312,51.49111704955784],[-3.1283943919027726,51.49105845544961],[-3.128428756340616,51.490999501529544],[-3.1297860133684527,51.49129946285451],[-3.1297430510375497,51.491373169050036]],[[-3.127579247494871,51.49326715021018],[-3.127481616031034,51.49331650839057],[-3.12738259966048,51.49336480528863],[-3.127282265173565,51.49341200743424],[-3.1271806020279906,51.49345812031042],[-3.1270776923943706,51.49350310660654],[-3.1262620825866807,51.49276239656504],[-3.1263444211299642,51.49272640327535],[-3.126344420690535,51.492726403001065],[-3.1264257237933393,51.492689525626474],[-3.126425724232768,51.492689525900765],[-3.1265059989231467,51.49265176087738],[-3.126506004635237,51.49265175813426],[-3.1265060156200155,51.492651752922285],[-3.126585205653794,51.49261312686066],[-3.126585206093227,51.49261312713494],[-3.1266633215747173,51.49257363564818],[-3.126663328604931,51.492573632082106],[-3.126663336513925,51.49257362796741],[-3.1267403198815584,51.492533299586306],[-3.127675493170836,51.4932167307509],[-3.127579247494871,51.49326715021018]],[[-3.12731340462681,51.49218170577075],[-3.1273134041873734,51.492181705496456],[-3.127246149571722,51.49222833888051],[-3.1261845467956157,51.49162114369728],[-3.126133126475859,51.491655569004514],[-3.12613312427897,51.49165557037606],[-3.126080757379829,51.49168942487464],[-3.1271077637448337,51.492319381672765],[-3.1270367045909193,51.492363745266466],[-3.1269644021553966,51.492407336440266],[-3.1269644017159552,51.49240733616599],[-3.1268908762104077,51.49245014147643],[-3.1268161948610826,51.492492122517554],[-3.12681619442166,51.49249212224328],[-3.1267403352600858,51.49253329108263],[-3.1258051864220358,51.491849854576586],[-3.1257474260857787,51.49188011248],[-3.1256888400024216,51.491909730440284],[-3.125629434763677,51.491938705438834],[-3.125569240688331,51.49196702321031],[-3.125508251186671,51.49199468677094],[-3.1254464992141573,51.49202168103248],[-3.1246309418360183,51.4912809597349],[-3.124672110651466,51.49126296339095],[-3.124712760933244,51.49124452514855],[-3.1247127613726535,51.49124452542285],[-3.1247529067412025,51.49122563952212],[-3.124792509408824,51.49120632324481],[-3.1248315588298508,51.49118658207758],[-3.1248315623449145,51.491186580157446],[-3.1248315662993775,51.491186578237304],[-3.1248700655491186,51.491166410534866],[-3.124907987385478,51.491145830561585],[-3.124907988703614,51.49114582973868],[-3.1249079944156053,51.49114582672133],[-3.124945342792775,51.49112483255767],[-3.124982090029459,51.491103439016605],[-3.1249820904688534,51.49110343929089],[-3.1250182493070264,51.49108164006396],[-3.125053778884163,51.49105945874193],[-3.125088696775503,51.491036885176065],[-3.125088696336098,51.491036884901774],[-3.125122971785142,51.49101393856822],[-3.125156592928826,51.49099062714807],[-3.1251565968832127,51.49099062440503],[-3.1251565995194692,51.490990622484915],[-3.125189570751749,51.49096694323566],[-3.12522186746782,51.490942913987595],[-3.125253494940499,51.490918530626715],[-3.125284435155806,51.490893807143024],[-3.1253146802054625,51.490868749846044],[-3.1253442195450347,51.4908433672397],[-3.125373044387645,51.490817666730706],[-3.125373050538815,51.490817661244655],[-3.12540115253708,51.49079165051401],[-3.125428523343665,51.49076533724268],[-3.125455154171978,51.490738729934584],[-3.125481032281244,51.49071184175668],[-3.1255061598691185,51.490684670240775],[-3.1255305233162183,51.49065722965105],[-3.1255541160331055,51.49062952739412],[-3.1255541177905375,51.49062952547401],[-3.125554120866038,51.49062952163379],[-3.1255769309910337,51.49060157224808],[-3.1255769327484564,51.490601569779386],[-3.1255769353846032,51.49060156676207],[-3.125598965554946,51.49057336723072],[-3.125620199076147,51.490544937578186],[-3.125620202151615,51.49054493346368],[-3.1256202030303166,51.49054493209219],[-3.1256406390249687,51.490516275610474],[-3.125640642100401,51.49051627094735],[-3.1256406429791066,51.490516269850154],[-3.125660278372858,51.49048738955701],[-3.125679099547143,51.49045830520247],[-3.1256971082606615,51.49042901459254],[-3.125714295727882,51.49039953199119],[-3.1257306557993485,51.4903698669993],[-3.127096056505354,51.49065502671313],[-3.1270715193235197,51.49069952064332],[-3.127045742553273,51.49074373976579],[-3.127018725752017,51.49078768325685],[-3.1269904882471407,51.49083132066805],[-3.1269610423369913,51.4908746325231],[-3.1269303774747286,51.49091763473064],[-3.1268985208964417,51.490960288064414],[-3.1268654800684454,51.49100258374571],[-3.1268312606996815,51.49104451436739],[-3.1267958689385518,51.49108607169929],[-3.1267593298257905,51.49112722776134],[-3.126721632375313,51.4911679929761],[-3.1267216319359403,51.49116799325042],[-3.126682813490828,51.49120832784263],[-3.1266428617472455,51.491248245252066],[-3.1266018140488723,51.491287707623556],[-3.126559652819634,51.49132673223714],[-3.1265164246304087,51.49136527547712],[-3.126472114541522,51.49140335188048],[-3.1264267480348225,51.491440937855764],[-3.126380330381621,51.49147802956144],[-3.12633288926123,51.4915146050518],[-3.1262844338996314,51.49155065719361],[-3.1273793324808774,51.49213435593519],[-3.12731340462681,51.49218170577075]],[[-3.123815380506196,51.489058758786676],[-3.1237945527656477,51.489049988847334],[-3.123794548371877,51.4890499869273],[-3.1237734745944814,51.48904144328049],[-3.1237521692794648,51.489033132234816],[-3.1237306262753664,51.48902505132152],[-3.123708868868879,51.48901720959209],[-3.123708867550753,51.48901720904351],[-3.1237088640357547,51.48901720794635],[-3.1236868913478784,51.48900960430351],[-3.1236647051358837,51.48900223984432],[-3.1236647033783873,51.48900223929574],[-3.1236647003027675,51.489002238472885],[-3.123642315505127,51.48899511813446],[-3.123642311550758,51.48899511676299],[-3.123619730803459,51.488988241368126],[-3.1236197281672218,51.488988240819545],[-3.123619725970349,51.48898823999668],[-3.123596952788099,51.4889816106424],[-3.1235740021093226,51.48897523144298],[-3.123550871736844,51.48896910212404],[-3.1235508682218605,51.48896910130116],[-3.1235508669037415,51.48896910102687],[-3.123527573094064,51.488963225702655],[-3.123504114528748,51.4889576040988],[-3.1234805021917866,51.48895223895811],[-3.123480497358682,51.48895223786096],[-3.1234567435521603,51.48894713165196],[-3.1239135628134775,51.48809476237694],[-3.123961079753454,51.48810497625226],[-3.124008303231091,51.48811570633693],[-3.1240552161124966,51.488126948517014],[-3.1241018096116857,51.48813870059863],[-3.1241480670340196,51.48815095819354],[-3.1241939857445966,51.48816372130217],[-3.124193986183962,51.48816372130217],[-3.1242395275196375,51.48817697867917],[-3.124284701147669,51.48819073306783],[-3.124329470601614,51.48820497349713],[-3.1243738451092695,51.48821970298469],[-3.1244178000669813,51.48823491302804],[-3.1244613284458262,51.48825060170766],[-3.1245043977332987,51.48826675695541],[-3.1245044008089033,51.48826675832685],[-3.1245470224294927,51.48828338453183],[-3.124589170021745,51.48830047154586],[-3.1246308277689874,51.488318012512295],[-3.123815380506196,51.489058758786676]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1231464567122256,51.48980029768287],[-3.122999906539917,51.48979949951172]],[[-3.1229999305416225,51.489799503960434],[-3.1229999270265814,51.48979950341185],[-3.1229999388898397,51.489799505606214],[-3.123011993717521,51.489801878818696],[-3.122999906539917,51.48979949951172],[-3.1229999169208416,51.48979950149177],[-3.122999906539917,51.48979949951172],[-3.1229999208752637,51.48979950231466],[-3.12299992570844,51.489799503137554],[-3.122999924390299,51.48979950286326],[-3.1229999199964986,51.48979950204036],[-3.1230850147300266,51.48981423419881],[-3.1229999305416225,51.489799503960434]],[[-3.122999920435883,51.48979950286326],[-3.122999906539917,51.48979949951172],[-3.123075026306517,51.48981610627907],[-3.122999920435883,51.48979950286326]],[[-3.1229999191177393,51.48979950615481],[-3.122999906539917,51.48979949951172],[-3.123063133628365,51.48983203742523],[-3.1229999191177393,51.48979950615481]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230603334095375,51.48979520133038],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229999147239456,51.48979949490866],[-3.1229999173602216,51.48979949353717],[-3.122999906539917,51.48979949951172],[-3.122999913845187,51.489799495731546],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999917799598,51.489799493811475],[-3.1230596413531124,51.48976983686138],[-3.122999906539917,51.48979949951172]],[[-3.1229999186783646,51.48979949737732],[-3.122999906539917,51.48979949951172],[-3.1230555586620694,51.489789864346236],[-3.1229999186783646,51.48979949737732]],[[-3.122999917360222,51.489799493811475],[-3.123004784371165,51.489796989210255],[-3.122999917799598,51.489799493811475],[-3.1229999151633288,51.48979949490866],[-3.122999914723944,51.48979949518295],[-3.122999906539917,51.48979949951172],[-3.122999919117746,51.48979949271428],[-3.1230399110185996,51.489778169722406],[-3.122999917360222,51.489799493811475]],[[-3.122999919557122,51.489799499023114],[-3.122999906539917,51.48979949951172],[-3.1230332201591526,51.489798227379914],[-3.122999919557122,51.489799499023114]],[[-3.1229999191177393,51.48979950258895],[-3.122999920435883,51.48979950286326],[-3.12302874683752,51.48980623107792],[-3.1229999191177393,51.48979950258895]],[[-3.122999912966419,51.48979949381147],[-3.1229999125270416,51.489799494085766],[-3.122999906539917,51.48979949951172],[-3.122999914723948,51.48979949161709],[-3.123025929954184,51.489773998774126],[-3.122999912966419,51.48979949381147]],[[-3.122999925269059,51.489799489148446],[-3.1229999147239456,51.48979949490866],[-3.1230189420661705,51.4897886105487],[-3.122999925269059,51.489799489148446]],[[-3.122999919117746,51.48979949271428],[-3.1229999173602216,51.48979949353717],[-3.122999925269059,51.489799489148446],[-3.1230188080555146,51.48978905984636],[-3.122999919117746,51.48979949271428]],[[-3.122999934496037,51.489799480370955],[-3.1229999287841097,51.48979948421108],[-3.1230172820887616,51.489787239066594],[-3.122999934496037,51.489799480370955]],[[-3.1229999322991424,51.48979948201672],[-3.122999934496037,51.489799480370955],[-3.123015093978063,51.4897891525591],[-3.1229999322991424,51.48979948201672]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230147284123317,51.48978379116025],[-3.122999906539917,51.48979949951172]],[[-3.122999912527045,51.48979948750264],[-3.1230111949183534,51.489775709289304],[-3.1229999107695283,51.4897994913428],[-3.122999910330144,51.489799492165695],[-3.1230142020302316,51.489767319931254],[-3.122999912527045,51.48979948750264]],[[-3.122999914723948,51.48979949161709],[-3.122999906539917,51.48979949951172],[-3.1230141914906273,51.489784947319954],[-3.122999914723948,51.48979949161709]],[[-3.122999906539917,51.48979949951172],[-3.12299991736022,51.4897994913428],[-3.123013656327324,51.48978908672812],[-3.122999906539917,51.48979949951172]],[[-3.122999915163316,51.489799501766065],[-3.122999906539917,51.48979949951172],[-3.122999916481459,51.48979950204036],[-3.1229999191177393,51.48979950258895],[-3.123013353598675,51.48980280648923],[-3.122999915163316,51.489799501766065]],[[-3.122999916042079,51.48979949353718],[-3.122999906539917,51.48979949951172],[-3.1230112089826116,51.489792319586634],[-3.122999916042079,51.48979949353718]],[[-3.122999919117742,51.48979949765162],[-3.1229999169208424,51.489799497925915],[-3.123011113638571,51.4897979481503],[-3.122999919117742,51.48979949765162]],[[-3.12299991736022,51.4897994913428],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230100428683536,51.48979154442491],[-3.12299991736022,51.4897994913428]],[[-3.1229999147239456,51.48979949463437],[-3.1229999164814606,51.48979949353718],[-3.123009739257355,51.489793674337065],[-3.1229999147239456,51.48979949463437]],[[-3.122999909451387,51.489799493811475],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.123009342493486,51.489774964300246],[-3.122999909451387,51.489799493811475]],[[-3.1229999388898397,51.489799505606214],[-3.1229999305416225,51.489799503960434],[-3.1230089518901454,51.489801172231246],[-3.1229999388898397,51.489799505606214]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230082168055695,51.48979033011456],[-3.122999906539917,51.48979949951172]],[[-3.1229999191177433,51.48979949106851],[-3.1229999160420836,51.48979949298858],[-3.122999906539917,51.48979949951172],[-3.122999913845183,51.48979949463437],[-3.1229999160420836,51.48979949298858],[-3.122999915602706,51.48979949353717],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.123007379348247,51.48979403750576],[-3.1229999191177433,51.48979949106851]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230057523243318,51.48979149176027],[-3.122999906539917,51.48979949951172]],[[-3.1229999090120057,51.48979949216569],[-3.123002218831768,51.48979171202047],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999908133246,51.48979949381147],[-3.1229999081332434,51.48979949298858],[-3.1230003114839806,51.48979758086785],[-3.122999908572625,51.48979949216568],[-3.1230049891200995,51.48977827012162],[-3.1229999090120057,51.48979949216569]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.123003926261847,51.48979241339651],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1230027522390125,51.48979386579637],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999907254485,51.48979949353718],[-3.1230006032321023,51.48978954644983],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.123000507886813,51.489799582683524],[-3.122999906539917,51.48979949951172]],[[-3.1229999257084455,51.48979948640547],[-3.1229999287841097,51.48979948421108],[-3.1229999322991424,51.48979948201672],[-3.1229999257084455,51.48979948640547]],[[-3.1229999235115447,51.48979948805124],[-3.1229999226327743,51.48979948859984],[-3.122999924390304,51.48979948722835],[-3.1229999235115447,51.48979948805124]],[[-3.1229999208752695,51.489799489971325],[-3.122999915602706,51.48979949353717],[-3.1229999191177433,51.48979949106851],[-3.1229999217540234,51.489799489148425],[-3.1229999226327743,51.48979948859984],[-3.1229999208752695,51.489799489971325]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229999169208424,51.489799497925915],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.1229999098907673,51.48979949326287],[-3.1229999098907624,51.48979949298858],[-3.122999910330144,51.489799492165695],[-3.1229999098907673,51.48979949326287]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.1229999098907624,51.48979949298858],[-3.122999909451387,51.489799493811475],[-3.1229999098907637,51.48979949271428],[-3.1229999098907624,51.48979949298858]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.12295844910747,51.48981796053995],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229918622075026,51.4898047915733],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999905057586,51.48979950780058],[-3.1229949725762536,51.48982289788369],[-3.122999906539917,51.48979949951172],[-3.122999905057586,51.48979950780058]],[[-3.122992426804906,51.48984048330472],[-3.122999905057586,51.48979950780058],[-3.122999906539917,51.48979949951172],[-3.122992426804906,51.48984048330472]],[[-3.1229602132032297,51.489835554190634],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229602132032297,51.489835554190634]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229998958306058,51.48979949682873],[-3.1229998927549514,51.489799496005844],[-3.1229998905580514,51.489799495457255],[-3.1229998874823868,51.48979949463437],[-3.1229998852854894,51.489799494085766],[-3.1229998822098275,51.48979949326288],[-3.1229998734222297,51.48979949106851],[-3.122999874740373,51.4897994913428],[-3.1229998694678134,51.489799489971325],[-3.122999862877108,51.48979948832553],[-3.122928545397545,51.489780129007244],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.1229965600584517,51.48979845779364],[-3.122999896269991,51.489799496280135],[-3.1229577219631346,51.48978579022631],[-3.122999906539917,51.48979949951172],[-3.1229965600584517,51.48979845779364]],[[-3.122999906539917,51.48979949951172],[-3.1229867724272853,51.48981460123763],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.1229869174239875,51.48981006739124],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122924419134795,51.48980908703206],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122962453614494,51.489821687407485],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122960609101872,51.48981573709347],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.1229986585367784,51.489800444797346],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.1229599482803065,51.4898088259193],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229599482803065,51.4898088259193]],[[-3.122999906539917,51.48979949951172],[-3.122816220712529,51.48975432883289],[-3.122999868149681,51.489799489697035],[-3.122999878694792,51.48979949243998],[-3.1229998800129355,51.48979949271428],[-3.122999883967356,51.489799493811475],[-3.1229998870430027,51.48979949463437],[-3.1229998892399093,51.48979949518296],[-3.1229998923155637,51.489799496005844],[-3.122999894512467,51.48979949655443],[-3.122999897588123,51.48979949737732],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.122999906539917,51.48979949951172],[-3.1229946922546548,51.48979726213524],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172]],[[-3.1229598230826365,51.48977959496686],[-3.1229998971487465,51.48979949463437],[-3.122999897588128,51.48979949490867],[-3.122999906539917,51.48979949951172],[-3.1229598230826365,51.48977959496686]],[[-3.122999894512468,51.489799499845994],[-3.1229370293515255,51.489801213359215],[-3.122999906539917,51.48979949951172],[-3.122999894512468,51.489799499845994]],[[-3.122999897588128,51.48979949490867],[-3.122999898466888,51.48979949518296],[-3.122999898906269,51.489799495457255],[-3.122999897588128,51.48979949490867]],[[-3.1229998949518483,51.48979949518296],[-3.122999895830607,51.489799495457255],[-3.122999906539917,51.48979949951172],[-3.1229998949518483,51.48979949518296]],[[-3.1229998971487465,51.48979949463437],[-3.122999896269986,51.489799494085766],[-3.122999898466888,51.48979949518296],[-3.1229998971487465,51.48979949463437]],[[-3.1229998923155713,51.48979949353718],[-3.1229946175600687,51.48979731397728],[-3.1229998984668796,51.489799496005844],[-3.1229998923155713,51.48979949353718]],[[-3.122999895830607,51.489799495457255],[-3.122999892754947,51.489799494360064],[-3.1229998883611505,51.489799492714276],[-3.1229911565649506,51.489796152057195],[-3.1229998918761854,51.48979949381147],[-3.12292401628936,51.489769317060215],[-3.1229998923155713,51.48979949353718],[-3.122999898466892,51.489799496280135],[-3.122999895830607,51.489799495457255]],[[-3.122999894512467,51.48979949655443],[-3.1229998936337084,51.489799496280135],[-3.1229998958306058,51.48979949682873],[-3.1229998967093673,51.48979949710302],[-3.122999894512467,51.48979949655443]],[[-3.122999889679291,51.48979949079421],[-3.122957393315392,51.48977683197761],[-3.122999896269986,51.489799494085766],[-3.122999889679291,51.48979949079421]],[[-3.1229998949518483,51.489799500120284],[-3.122986492107007,51.489800305728274],[-3.122999891436809,51.48979950012029],[-3.122969523693721,51.489800992014885],[-3.122999894512468,51.489799499845994],[-3.122999896269987,51.489799499845994],[-3.1229998949518483,51.489799500120284]],[[-3.1229427180373666,51.489774033598735],[-3.1229998826492067,51.489799488599836],[-3.1229998953912244,51.489799494360064],[-3.1229427180373666,51.489774033598735]],[[-3.1229998826492067,51.489799488599836],[-3.1229805648761126,51.48979056491103],[-3.12299988660363,51.48979949024562],[-3.1229998826492067,51.489799488599836]],[[-3.122999883967356,51.489799493811475],[-3.1229998830885903,51.48979949353718],[-3.1229998852854894,51.489799494085766],[-3.122999883967356,51.489799493811475]],[[-3.1229745440531396,51.489792941140244],[-3.122999862877108,51.48979948832553],[-3.122999868149681,51.489799489697035],[-3.1229745440531396,51.489792941140244]],[[-3.121452686373269,51.49131559243724],[-3.121410545521726,51.49129850787088],[-3.121368877066869,51.491280961655065],[-3.1213277034181552,51.491262963390966],[-3.1212870443486365,51.49124452158259],[-3.1212469152373408,51.491225643362405],[-3.121207304221415,51.49120632297052],[-3.1211682486490004,51.49118657878595],[-3.121168247330861,51.49118657796302],[-3.121129751596187,51.491166411906406],[-3.1210918227297033,51.491145828367166],[-3.121054478746342,51.49112483667224],[-3.1210544765494324,51.49112483557501],[-3.1210177156918992,51.491103434627725],[-3.120981571792691,51.49108164417854],[-3.121990753457291,51.49044057597751],[-3.1220088291244155,51.490451473380766],[-3.1220272066627555,51.49046217219308],[-3.1220458772846733,51.49047266692833],[-3.1220458794815755,51.49047266802554],[-3.122045880799718,51.49047266884844],[-3.122064841429472,51.49048295868357],[-3.1220840911882575,51.49049304224697],[-3.122084090748872,51.49049304224695],[-3.122103619530827,51.49050291460103],[-3.1221234211845093,51.49051257272836],[-3.122123420745122,51.490512572728356],[-3.122143493073502,51.4905220155316],[-3.1221638185011527,51.49053123450732],[-3.12216381937992,51.49053123478161],[-3.122163822455605,51.49053123615311],[-3.1221844066943514,51.49054023404416],[-3.1222052391988413,51.49054900646156],[-3.122226309423702,51.490557548467834],[-3.121452686373269,51.49131559243724]],[[-3.1219730101640106,51.48916950845512],[-3.1219555544619317,51.48918079258485],[-3.121938419929355,51.48919226323649],[-3.1219216061269153,51.48920392068448],[-3.1219216048087763,51.48920392150736],[-3.1219216026118612,51.48920392315311],[-3.121905120084774,51.489215759717375],[-3.121888967954327,51.489227776495234],[-3.1218731510537894,51.489239969921],[-3.121857683443522,51.4892523290231],[-3.121857683004135,51.48925232929739],[-3.121857680367835,51.48925233149174],[-3.121842557654108,51.48926485956182],[-3.1218277908216407,51.48927754782263],[-3.1218133776736705,51.48929039764578],[-3.1218133745979673,51.48929040038872],[-3.1217993256798695,51.48930340299694],[-3.1217993230435583,51.48930340546558],[-3.1217856396736496,51.48931655921324],[-3.1145005760571114,51.486418652253626],[-3.114596385658473,51.48632656543335],[-3.1146947393688773,51.48623555148772],[-3.1147956486051895,51.48614559834212],[-3.114899048325804,51.48605676605653],[-3.1150049086441967,51.48596907903473],[-3.1151132168114173,51.48588254796685],[-3.1152238998811392,51.48579723126648],[-3.1153369802582844,51.48571311109917],[-3.115452381483746,51.485630245604],[-3.1155700684034913,51.48554865808716],[-3.1156900339847753,51.48546835457588],[-3.1158122356054063,51.4853893641361],[-3.115936600766586,51.48531173311263],[-3.1219907782479623,51.489158416333005],[-3.1219730101640106,51.48916950845512]],[[-3.1197153700644016,51.49155064978736],[-3.119666923929624,51.4915146047775],[-3.1196194744612016,51.491478022703795],[-3.119619474021803,51.49147802270378],[-3.119573073064684,51.49144094389052],[-3.119527699528096,51.49140335188051],[-3.1194833938328297,51.49136527986603],[-3.119440160371244,51.49132673168855],[-3.120626769242604,51.490817666182124],[-3.120655588812775,51.490843362027974],[-3.120655588373388,51.490843362027974],[-3.1206555892521415,51.49084336257658],[-3.120655594085193,51.49084336723972],[-3.1206555945245937,51.49084336696543],[-3.120685127712951,51.490868744634305],[-3.1206851277129313,51.49086874490861],[-3.1207153723232053,51.49089380193129],[-3.1207153723232057,51.49089380220558],[-3.1207153762775652,51.49089380522292],[-3.1207153780350483,51.49089380686873],[-3.120715378474435,51.49089380686875],[-3.1207463186897066,51.49091853090102],[-3.12074631912912,51.490918530626736],[-3.1207779400111932,51.49094290850156],[-3.1207779395717856,51.49094290877584],[-3.120777943965552,51.4909429117932],[-3.1207779461624288,51.490942913439014],[-3.120810243757253,51.49096694323569],[-3.1197153700644016,51.49155064978736]],[[-3.118142571125239,51.491731124190444],[-3.1180893116411696,51.491677909296946],[-3.1180893107624583,51.491677908474024],[-3.1180375546424175,51.49162412856891],[-3.11798730100422,51.4915697836541],[-3.1179385762050194,51.491514901985205],[-3.117891387270695,51.4914594895989],[-3.117845763632683,51.491403582979785],[-3.117801710558746,51.491347188164404],[-3.117759236831034,51.49129031694963],[-3.119069436594917,51.49091763445639],[-3.1191012861436906,51.490960279012434],[-3.1191012857042977,51.49096027901242],[-3.1191012887796794,51.49096028312696],[-3.1191012922944092,51.490960287790124],[-3.119101292733831,51.49096028751583],[-3.1191343361978507,51.49100258703738],[-3.1191343366372486,51.4910025870374],[-3.119168557763373,51.49104452012779],[-3.119168558202784,51.4910445198535],[-3.1192039394194913,51.49108606539032],[-3.119203938980099,51.4910860653903],[-3.1192039420555497,51.491086068956264],[-3.1192039464490597,51.49108607389373],[-3.119203946449078,51.49108607361944],[-3.119240484243863,51.49112722721279],[-3.1192404838044583,51.49112722748707],[-3.119240485561882,51.491127229132914],[-3.1192404912734744,51.491127235441894],[-3.119278174225299,51.491167984747015],[-3.1192781737858946,51.49116798474701],[-3.1192781816943067,51.49116799297614],[-3.1192781816943334,51.49116799270185],[-3.119316997942642,51.4912083253739],[-3.119316997503242,51.49120832537388],[-3.1193170040936087,51.49120833195722],[-3.119317005411685,51.49120833332873],[-3.11931700585108,51.49120833332874],[-3.119356943535171,51.49124823674863],[-3.118142571125239,51.491731124190444]],[[-3.1150185519347433,51.49186298742551],[-3.114962096941936,51.4917757323604],[-3.114908069951257,51.4916878429868],[-3.1148565232242027,51.49159940352063],[-3.114807444448996,51.49151039256918],[-3.1147608731456264,51.491420882278234],[-3.114716809300704,51.49133087374795],[-3.114675268713137,51.49124039934923],[-3.116062744988755,51.49100029044197],[-3.1160973589694128,51.491075674076804],[-3.1160973585300145,51.49107567407678],[-3.1161340822061954,51.49115068450653],[-3.1161340844024177,51.491150689443984],[-3.116134089673609,51.49115069959326],[-3.1161340901130457,51.49115069931899],[-3.1161729050454667,51.49122529813909],[-3.116172905484858,51.49122529813911],[-3.116213792354967,51.49129944749348],[-3.1162137919155257,51.491299447767766],[-3.116213798065538,51.49129945791709],[-3.1162138007012152,51.49129946258027],[-3.116256754685591,51.4913731553348],[-3.1162567542461566,51.49137315560907],[-3.1162567603962747,51.4913731652098],[-3.1162567630319202,51.49137317014729],[-3.1162567634713523,51.49137316987301],[-3.1163017714001255,51.491446383806654],[-3.1163017705213347,51.49144638380661],[-3.1163488275708,51.491519110413584],[-3.1150185519347433,51.49186298742551]]],[[[-3.135996080090974,51.49366826085528],[-3.1346963637190908,51.4932814495082],[-3.134597229114068,51.49340837982029],[-3.1358859338184195,51.49380929558869],[-3.135996080090974,51.49366826085528]]],[[[-3.135933440380829,51.48930495899101],[-3.1359176252552445,51.48916411990224],[-3.1359176239344086,51.48916411030202],[-3.1359176221719522,51.48916409274723],[-3.135917621732579,51.48916409274728],[-3.135897875262651,51.48902347626555],[-3.135897875702024,51.4890234762655],[-3.1358978739412042,51.48902346447095],[-3.135897871300173,51.48902344746486],[-3.1358978708608,51.48902344746491],[-3.1358741922000712,51.4888830404221],[-3.1344437418365634,51.488984939008105],[-3.134464788750039,51.48910976627815],[-3.134482339281743,51.48923477409691],[-3.134496395164699,51.48935997837495],[-3.135933440380829,51.48930495899101]]],[[[-3.132499719300091,51.487698910843086],[-3.132439528115322,51.4875957077756],[-3.1310910667002667,51.48791058169932],[-3.1311426564971323,51.48799904206189],[-3.132499719300091,51.487698910843086]]],[[[-3.130555431559462,51.481110426945264],[-3.130311410540802,51.48102943184502],[-3.1303114105407146,51.481029431296434],[-3.1303113679268946,51.481029417858544],[-3.130311360019134,51.48102941511606],[-3.130065113053107,51.48095109101842],[-3.1300651126138903,51.480951091567036],[-3.129816707762464,51.480875460397975],[-3.1291971085436914,51.481686753571395],[-3.1289694492320907,51.48162048291661],[-3.128969446156877,51.48162048209388],[-3.1289694026643957,51.481620469478436],[-3.128969402225132,51.48162046975275],[-3.1287399256208337,51.48155668935967],[-3.1287399260601,51.481556689085345],[-3.1287398970652887,51.48155668140645],[-3.128739878614044,51.48155667647002],[-3.1285086549775336,51.48149540607413],[-3.12850865541677,51.481495405525514],[-3.1282757005322255,51.481436650053276],[-3.128275700971493,51.481436649778956],[-3.128275679445249,51.48143664484259],[-3.128275659676165,51.48143663963184],[-3.128275659236899,51.48143663990616],[-3.128041146600562,51.481380442953],[-3.1280411470398337,51.48138044267868],[-3.1280411158488146,51.4813804355483],[-3.1280410982764066,51.48138043143461],[-3.1280410982764346,51.4813804317089],[-3.1278050581669987,51.481326800121714],[-3.128285475006454,51.4804795185912],[-3.1280241723494777,51.480423351052046],[-3.1280241723494164,51.48042335050345],[-3.1280241345696482,51.48042334282476],[-3.128024118754856,51.480423339533864],[-3.1277612861143007,51.480370027453475],[-3.1273285144427954,51.48122726070847],[-3.127567474734965,51.4812757308748],[-3.1278050089644474,51.481326789426156],[-3.1273245791520514,51.48217406941379],[-3.127537084025414,51.482222353536486],[-3.127748171062363,51.48227293703575],[-3.1272206631522446,51.483109231672906],[-3.1274070306362622,51.48315623709263],[-3.127592060548807,51.48320526740133],[-3.1277757041445646,51.483256309166784],[-3.1279578621544375,51.483309335244236],[-3.1281385420655403,51.48336434728606],[-3.12813854206552,51.48336434701175],[-3.1283176147318583,51.4834213058061],[-3.1284951043337674,51.48348021903945],[-3.1286709019320766,51.48354105024274],[-3.1288449974374055,51.48360379640677],[-3.1290172797111198,51.48366841749743],[-3.1291877470093232,51.48373491242553],[-3.12935632861034,51.483803253773125],[-3.129522990257044,51.48387342810961],[-3.130338256952738,51.483132643144266],[-3.130150782828679,51.48305370607438],[-3.1301507678910343,51.483053700040756],[-3.1301507441664227,51.48305368989323],[-3.130150744166476,51.48305369016752],[-3.1299611311051168,51.48297682331606],[-3.129961128469061,51.48297682221902],[-3.129961092443078,51.48297680768342],[-3.129961092003842,51.48297680823206],[-3.1297693606390484,51.482902018993514],[-3.130521396584345,51.48213560733866],[-3.130305985666253,51.482054810365064],[-3.1303059852269937,51.48205481063938],[-3.1300884329516965,51.48197640307257],[-3.1300884333909558,51.48197640279826],[-3.1300884065919288,51.48197639347378],[-3.130088388579415,51.48197638689175],[-3.130088388140148,51.48197638716607],[-3.1298686843799044,51.48190036350881],[-3.130555431559462,51.481110426945264]]],[[[-3.124566884181321,51.377450850228385],[-3.121432925064509,51.37745085022832],[-3.1214454305165575,51.378349648049436],[-3.1214456181140076,51.37834964805194],[-3.1183382312304793,51.37838350929214],[-3.118338231230758,51.37838350654913],[-3.1183379669279643,51.378383512298946],[-3.118337591732371,51.37838351639852],[-3.118337591731953,51.37838352051305],[-3.115231163186857,51.378451242507005],[-3.115293663437633,51.37934922288266],[-3.1183751008681875,51.379282045759616],[-3.1184501222325474,51.381079091686445],[-3.121483133913565,51.38104604094955],[-3.1245168598720787,51.381046040947204],[-3.1245418727872476,51.37924844573061],[-3.121458570708031,51.37924844573892],[-3.1214460700134707,51.37834964805792],[-3.1245543787291967,51.3783496480495],[-3.124566884181321,51.377450850228385]]],[[[-3.1229999090120057,51.48979949216569],[-3.1229999085726248,51.48979949326288],[-3.1229999085726248,51.48979949353718],[-3.1229999090120057,51.48979949216569]]],[[[-3.1229999085726248,51.48979949353718],[-3.122999906539917,51.48979949951172],[-3.122999906539917,51.48979949951172],[-3.1229999085726248,51.48979949353718]]],[[[-3.116839290983609,51.34244236994229],[-3.116764405790065,51.34064530667811],[-3.1126090974183955,51.34073589527748],[-3.112609096980628,51.34073589445452],[-3.1126090098273513,51.34073589746412],[-3.1126083568365934,51.34073591166991],[-3.112608357710885,51.34073591880188],[-3.108457037685233,51.34087174077069],[-3.1084570372481473,51.34087173802759],[-3.1084567775377603,51.34087174951605],[-3.108456183665931,51.34087176891777],[-3.108456184539753,51.340871775501164],[-3.1043085117042395,51.341052839707835],[-3.104532988331071,51.34284500835394],[-3.1086314266530644,51.342666093346736],[-3.112733806772836,51.34253187246631],[-3.116839290983609,51.34244236994229]]],[[[-3.1166303945380345,51.49344271476944],[-3.1165294941225734,51.49337275024851],[-3.1164305807597583,51.49330170776674],[-3.115335595206418,51.493885373199774],[-3.1154509914507447,51.49396825519001],[-3.1154509910113237,51.49396825518998],[-3.115450994526281,51.49396825765887],[-3.115451013858447,51.49396827164923],[-3.1154510142979155,51.49396827137496],[-3.115568710656234,51.49404988340444],[-3.1166303945380345,51.49344271476944]]],[[[-3.1053551045490937,51.49369858663],[-3.1052487965060394,51.49350571066602],[-3.105147918302578,51.49331174454928],[-3.10377457455407,51.49358180453987],[-3.1038832286352624,51.493790734314004],[-3.1038832299536305,51.49379073403992],[-3.1039976872169173,51.49399840644899],[-3.1053551045490937,51.49369858663]]],[[[-3.105118491674686,51.49560852278276],[-3.104958820414999,51.49541279648961],[-3.104804678802269,51.495215401189114],[-3.1048046792417034,51.49521540118918],[-3.1046560505317524,51.49501631468237],[-3.104513013723915,51.49481564123504],[-3.1043756328781997,51.494613472766915],[-3.1042439355993174,51.49440985181807],[-3.1041179099676053,51.49420475865622],[-3.1027690567992057,51.49451930382468],[-3.102904051398208,51.494739003768935],[-3.1029040500796845,51.4947390043173],[-3.1030451678863242,51.49495719980179],[-3.103045167446786,51.494957200076016],[-3.103045181499427,51.494957220924924],[-3.103045196429974,51.49495724396833],[-3.1030451968695174,51.49495724369412],[-3.1031923659383964,51.49517382551483],[-3.1018716978917698,51.49553199490301],[-3.1020351425058488,51.49576131775506],[-3.102035141626853,51.4957613180292],[-3.1020351622679483,51.495761345188214],[-3.102035175003199,51.495761363568334],[-3.1020351758822,51.495761363294186],[-3.102204974641157,51.49598882483896],[-3.1022049733227117,51.49598882511303],[-3.102381155725856,51.496214463181275],[-3.1023811425497856,51.496214447269764],[-3.099803390064194,51.4970160604949],[-3.100008680949127,51.497267742109315],[-3.102563632692078,51.496438160330115],[-3.1023811794425815,51.49621449226083],[-3.103670019358573,51.49581366134566],[-3.103841073558715,51.49602334852719],[-3.105118491674686,51.49560852278276]]],[[[-3.1018717290703486,51.49553204099038],[-3.101714703520345,51.495300943821185],[-3.1015641716073774,51.49506818209251],[-3.100224274273443,51.49539734454334],[-3.100384195868432,51.4956446365172],[-3.1003841949894224,51.495644636791326],[-3.1003842121150744,51.49564466175559],[-3.1003842279225653,51.495644686445296],[-3.1003842288015653,51.49564468617116],[-3.100551008705418,51.49589014939899],[-3.0992302992586573,51.496248288728246],[-3.09941418064866,51.496506306884385],[-3.0994141617657123,51.49650628137105],[-3.098103639352412,51.49687874118067],[-3.0983053375098093,51.4971489655201],[-3.0983052962300563,51.497148912572264],[-3.097005359869073,51.49753557962024],[-3.097225548025879,51.49781761668412],[-3.0972255471468038,51.49781761695823],[-3.097225565591479,51.497817639180255],[-3.097225591939711,51.497817673198774],[-3.097225593258235,51.49781767292476],[-3.0974536364459055,51.49809726811669],[-3.098731170072801,51.49768251208467],[-3.0985144968173572,51.49741686639775],[-3.098305338388112,51.49714896661748],[-3.0996052505561806,51.49676228239375],[-3.0994141999710467,51.49650633239781],[-3.1007246975090967,51.496133855452705],[-3.100551043397343,51.49589019768177],[-3.1018717290703486,51.49553204099038]]],[[[-3.0992303339475904,51.496248340577246],[-3.099053679181225,51.49598833248055],[-3.097723142921537,51.49633201278251],[-3.097909605555997,51.496606468031565],[-3.0992303339475904,51.496248340577246]]],[[[-2.998405762765002,51.35730142591618],[-2.9962263216373186,51.354975691600806],[-2.9924854473504925,51.35637747054931],[-2.992485446476628,51.35637746972543],[-2.992485380688125,51.35637749543655],[-2.9924847925656923,51.35637771586889],[-2.992484799119267,51.356377722185165],[-2.9887841013262832,51.357819750570414],[-2.988014974494483,51.357060791788754],[-2.98433315974879,51.35855167581666],[-2.9835430462736587,51.357801199123145],[-2.979882335344959,51.35934069710121],[-2.979882334033142,51.359340696276696],[-2.979882256378175,51.359340730195306],[-2.9798815812091686,51.35934101409612],[-2.9798815895105335,51.359341021512456],[-2.976264845830476,51.36091984626152],[-2.9762648418968256,51.36091984323925],[-2.9762645580051785,51.360919971805686],[-2.9762641012574886,51.36092017119986],[-2.976264106936698,51.36092017641873],[-2.97269093871103,51.36253850128812],[-2.973542611401222,51.3632621264409],[-2.9770955047048404,51.361652971160126],[-2.9787583741638395,51.363118537997146],[-2.9823142886827188,51.36156623158994],[-2.983125232642125,51.36230794926993],[-2.9867028682743717,51.36080334508645],[-2.987493080248173,51.361553790202734],[-2.9910914198877556,51.36009666874917],[-2.994729198231848,51.35867913104544],[-2.998405762765002,51.35730142591618]]],[[[-2.9802135271109926,51.38873555324984],[-2.9783139617715815,51.387386848767946],[-2.975476758407945,51.38897882847854],[-2.974508365622331,51.38831491917324],[-2.9716974369079288,51.389948221426906],[-2.9697239789689367,51.388641665330084],[-2.966922698003074,51.39032704526571],[-2.9669226830648703,51.390327054572055],[-2.9669221198477573,51.390327393406714],[-2.9669221294716372,51.39032739972848],[-2.964168643279438,51.39204269445261],[-2.963146123089714,51.39141130129567],[-2.960422124385307,51.39316829183022],[-2.962502116481393,51.39440858838457],[-2.9651906392759906,51.39267443035267],[-2.966213209215873,51.39330579729623],[-2.968931721857032,51.391612242981836],[-2.9699365510777302,51.39225464553304],[-2.972683623841987,51.390601815726626],[-2.973670423084305,51.39125506802562],[-2.976444620025213,51.38964306996296],[-2.9774130627572735,51.39030695460576],[-2.9802135271109926,51.38873555324984]]],[[[-2.967465428384443,51.367299668218855],[-2.9656825044864217,51.36589041164084],[-2.9622480433266047,51.36762363888164],[-2.964069958836051,51.36901325138252],[-2.967465428384443,51.367299668218855]]],[[[-2.966803722646043,51.3710972712724],[-2.9658926725466834,51.370402488055305],[-2.962583688018022,51.37213273967278],[-2.9635139614137733,51.372817489095745],[-2.9635142516314605,51.37281733725329],[-2.960273094335082,51.374572928963175],[-2.9621716056775536,51.37592192494399],[-2.965375259306948,51.37418660930063],[-2.963514642831559,51.372817132882346],[-2.966803722646043,51.3710972712724]]],[[[-2.9621236327986042,51.390779899974525],[-2.9611011710916304,51.390148489939236],[-2.9583427338906736,51.391927642791146],[-2.956262960218215,51.39068726036491],[-2.953519957056345,51.39251911841239],[-2.9535201377210423,51.39251899331804],[-2.9524631514142343,51.391910215412764],[-2.94975480407597,51.39378316191036],[-2.949754797511534,51.393783158609025],[-2.9497544734889662,51.39378339073738],[-2.9497542422546883,51.39378355058739],[-2.9497542470673643,51.39378355333752],[-2.9470988894770027,51.39568532962768],[-2.947098885537042,51.395685327975954],[-2.947098714482491,51.39568545472061],[-2.9470983389892096,51.395685723794955],[-2.947098346866001,51.395685727921304],[-2.9444962224928863,51.39761615115744],[-2.944496212426727,51.39761614620466],[-2.9444957475429328,51.39761650373373],[-2.944495688614825,51.39761654725783],[-2.9444956899271024,51.397616548082745],[-2.941947072343388,51.399575435138864],[-2.943069423105674,51.400136699435805],[-2.9456021956159195,51.3981899554861],[-2.946708726347308,51.398763351285154],[-2.949279027039614,51.39685646919574],[-2.9503694016555437,51.397441820403685],[-2.9529760109801075,51.39557486363031],[-2.955633647680802,51.393736897818386],[-2.9566907146754535,51.394345640348995],[-2.9593821670194025,51.39254812727497],[-2.9593821705191465,51.39254812947427],[-2.9621236327986042,51.390779899974525]],[[-2.9519024644020457,51.394977489621965],[-2.9519024578372894,51.39497748632075],[-2.951902148765031,51.394977707779084],[-2.951901909179794,51.39497787338143],[-2.9519019139927285,51.39497787613148],[-2.9492793836591473,51.396856204200986],[-2.9492795591093746,51.396856074168085],[-2.948189212347269,51.3962707078249],[-2.950828290772898,51.394380558450656],[-2.953519584738422,51.39251937599132],[-2.9545766021807567,51.3931281418406],[-2.9519024644020457,51.394977489621965]]],[[[-2.9433897383646865,51.39704272611517],[-2.942283291532038,51.39646929552284],[-2.9397024523562947,51.39845287468824],[-2.940824748613966,51.39901416031405],[-2.9433897383646865,51.39704272611517]]],[[[-2.930725549792471,51.39396183110638],[-2.928481485335142,51.39283905003917],[-2.925801337111175,51.39497334505306],[-2.925801571649367,51.39497315151672],[-2.923526594964637,51.39387494717171],[-2.9208755026353224,51.39606332664732],[-2.9231808235862693,51.39713657196473],[-2.9258010365654705,51.39497359361288],[-2.928076121799425,51.396071744820944],[-2.930725549792471,51.39396183110638]]]]} \ No newline at end of file +{"type":"MultiPolygon","coordinates":[[[[-3.1074798166986866,51.4917696154967],[-3.1074271305111805,51.49160022109984],[-3.1060113627696273,51.49176382050571],[-3.1060688331116597,51.491948615080155],[-3.1060688323780985,51.4919486151657],[-3.1061314608186006,51.492132755442455],[-3.107537230396692,51.491938410145],[-3.1074798160262596,51.49176961557513],[-3.1074798166986866,51.4917696154967]]],[[[-3.1089994702314514,51.491896904829],[-3.1089429884158366,51.49174404794686],[-3.10753723083233,51.491938410094555],[-3.107599355943079,51.49210655340204],[-3.1089994702314514,51.491896904829]]],[[[-3.1104542479217945,51.49182423529681],[-3.1103995716300576,51.491687239550195],[-3.108999470181035,51.491896904845476],[-3.109060217252437,51.4920491230595],[-3.1090602174612885,51.49204912299249],[-3.1091252113218504,51.49220065616192],[-3.110512746436559,51.49196061440089],[-3.1104542481097557,51.491824235236486],[-3.1104542479217945,51.49182423529681]]],[[[-3.107979931664541,51.492935716022984],[-3.1078945644727445,51.49277159287955],[-3.106521254158103,51.49304168664287],[-3.106614377204615,51.49322073115065],[-3.107979931664541,51.492935716022984]]],[[[-3.109427240344145,51.49279901868077],[-3.1093454697534986,51.492650685043515],[-3.10797993239081,51.4929357160891],[-3.1080698757556426,51.493098884125786],[-3.108069875470959,51.49309888379163],[-3.108164367643033,51.49326104701962],[-3.1081643680195854,51.493261047213345],[-3.1082633802722928,51.49342215621811],[-3.1096031610017447,51.493092900394565],[-3.10951314618913,51.492946438686374],[-3.109513145846811,51.492946438510266],[-3.1094272400853287,51.492799018377],[-3.109427240344145,51.49279901868077]]],[[[-3.097544439558189,51.496055651164674],[-3.0973734742494186,51.49577735642157],[-3.0960245289831967,51.496091825190874],[-3.0962044851149932,51.49638476907181],[-3.097544439558189,51.496055651164674]]],[[[-3.097453612760197,51.49809724046619],[-3.0972255694204542,51.49781764520754],[-3.095936615176876,51.498218403513285],[-3.096176054575588,51.49851198113336],[-3.097453612760197,51.49809724046619]]],[[[-3.09905369610311,51.49598835909296],[-3.0988843736642386,51.49572651818876],[-3.0975444384633684,51.49605565143578],[-3.097723160944509,51.49633204107506],[-3.096392605694636,51.49667570760986],[-3.0965888359949854,51.49696455222908],[-3.097909587347105,51.49660644108334],[-3.0981036588300075,51.496878768405956],[-3.098103658888377,51.496878768570596],[-3.098305317536937,51.49714893993851],[-3.0983053164717296,51.497148939885406],[-3.0985145009577693,51.49741687297538],[-3.0998034100839793,51.49701608602257],[-3.1000086595594754,51.497267717300595],[-3.100008660437226,51.49726771765254],[-3.1002209167113195,51.497517074335114],[-3.1027522954756344,51.4966597850023],[-3.1025636143789543,51.496438138675906],[-3.1025636135987273,51.49643813836308],[-3.102381160315484,51.49621447007589],[-3.103670001441368,51.495813641027155],[-3.103504837521388,51.49560212213521],[-3.1035048383623187,51.49560212217709],[-3.1033456150891054,51.49538883602164],[-3.103345615043008,51.49538883589167],[-3.103192380856811,51.495173847456186],[-3.1031923799379584,51.4951738474347],[-3.1030451814444637,51.49495722222063],[-3.1030451815040014,51.49495722247112],[-3.1029040634631744,51.494739026497825],[-3.101564186164534,51.49506820571939],[-3.1017147063831896,51.495300949790575],[-3.1017147063196835,51.49530094952339],[-3.1018717125495083,51.49553201817859],[-3.1018717135296345,51.49553201820153],[-3.1020351580333836,51.49576134101791],[-3.100724679713444,51.496133831626636],[-3.1009051219468566,51.49637555991978],[-3.0996052307411284,51.49676225714486],[-3.0994141799819848,51.49650630743145],[-3.1007246796612096,51.49613383147933],[-3.1005510255021007,51.49589017405478],[-3.09923031567186,51.49624831498969],[-3.0990536960316843,51.49598835879237],[-3.09905369610311,51.49598835909296]]],[[[-3.106332466627341,51.49837405570434],[-3.1060952841185188,51.49819115481347],[-3.104968088557466,51.49875051143494],[-3.1052210815851335,51.498945608113516],[-3.106332466627341,51.49837405570434]]],[[[-3.1181425759948733,51.49173112974229],[-3.118089315718107,51.491677913960636],[-3.116861604949692,51.49214748535352],[-3.11692817921221,51.492214005768204],[-3.116928179289846,51.49221400582961],[-3.116996603354091,51.49227979076988],[-3.1169966029912555,51.49227979066343],[-3.117066856014544,51.492344820092164],[-3.117066856167307,51.49234482018728],[-3.1171389167597816,51.49240907428702],[-3.117138916793093,51.49240907418597],[-3.1172127630027417,51.49247253338232],[-3.1172127628318957,51.49247253324781],[-3.117288372167042,51.492535178209394],[-3.11728837223905,51.49253517849683],[-3.1173657216832793,51.492596990135375],[-3.117365722126372,51.49259698998085],[-3.117444788108588,51.49265794946048],[-3.1174447878661935,51.492657949345435],[-3.1175255463055467,51.492718038101394],[-3.11752554611766,51.49271803824319],[-3.1176079724235795,51.4927772379678],[-3.1165295016075802,51.493372755834095],[-3.116630384174691,51.49344270792971],[-3.116630384053118,51.4934427079037],[-3.1167332073016514,51.493511550207835],[-3.1188222201455877,51.49227423744116],[-3.1187536710368344,51.49222834379295],[-3.118753671117883,51.49222834381029],[-3.11868641565928,51.492181710297345],[-3.118686415450071,51.492181710228294],[-3.118620474129955,51.49213435105474],[-3.1186204742802635,51.492134350941306],[-3.118555867185251,51.49208628053931],[-3.118555867379165,51.492086280631355],[-3.118492614182152,51.492037513646345],[-3.118492613827682,51.49203751376996],[-3.1184307337938275,51.491988065053],[-3.1184307337362136,51.49198806482307],[-3.1183702457229865,51.49193794944214],[-3.118370245859665,51.49193794954974],[-3.118311168280854,51.49188718277476],[-3.118311168254209,51.49188718285561],[-3.118253519103912,51.491835780150964],[-3.118253518981696,51.49183578007485],[-3.1181973158223464,51.49178375709935],[-3.1181973161126098,51.491783757184514],[-3.118142576056986,51.49173112979142],[-3.1181425759948733,51.49173112974229]]],[[[-3.1158801666154567,51.492853848059084],[-3.115795864098734,51.492775811893395],[-3.114595099144387,51.4932718207888],[-3.114693450783988,51.49336286397518],[-3.114693450997864,51.49336286410838],[-3.1147943334597636,51.493452821860984],[-3.1147943335064046,51.49345282171952],[-3.1148977160606717,51.49354166663175],[-3.114897715821481,51.49354166644345],[-3.115003566982361,51.49362937144891],[-3.1150035670831624,51.493629371851306],[-3.115111854630006,51.49371591022389],[-3.1151118552503605,51.49371591000757],[-3.1152225461856884,51.493801255374514],[-3.1141113831440275,51.49437289227437],[-3.1142405945849667,51.49446903790063],[-3.1142405942843197,51.494469038127484],[-3.1143724747206454,51.4945637613237],[-3.114372475139079,51.49456376146181],[-3.1145069844002737,51.49465703335763],[-3.114506984238173,51.49465703332296],[-3.114644081349563,51.494748825504274],[-3.1156886584646104,51.494130192540055],[-3.1155686982500073,51.49404987544999],[-3.115568698391837,51.494049875480336],[-3.115451002425857,51.49396826363681],[-3.1165295012937526,51.49337275573051],[-3.1164305901398226,51.49330171515177],[-3.116430590365304,51.49330171498163],[-3.1163336807534687,51.493229607570306],[-3.116333681044343,51.49322960770836],[-3.1162388024826755,51.49315645543476],[-3.1162388019509537,51.49315645562019],[-3.1161459833357066,51.493082280763076],[-3.116145983249301,51.49308228041818],[-3.1160552528649217,51.49300710558174],[-3.1160552530699364,51.49300710574314],[-3.115966638535108,51.49293095383438],[-3.115966638495139,51.492930953955636],[-3.1158801667987817,51.49285384817325],[-3.1158801666154567,51.492853848059084]]],[[[-3.163433421122041,51.560961948827895],[-3.1614348960734957,51.5613916205167],[-3.1618931822489422,51.562243826466535],[-3.163915532451357,51.56180903160241],[-3.163433421122041,51.560961948827895]]],[[[-3.1644080544675472,51.55882715712456],[-3.1624692551910463,51.559267773861826],[-3.163433424182477,51.56096194564523],[-3.165419575002485,51.56051056557703],[-3.165419577314679,51.56051056828086],[-3.1673927466212675,51.560037617973805],[-3.167922058209482,51.560873678189],[-3.169905001130044,51.56037340788444],[-3.169905002505838,51.56037340811477],[-3.171873591310767,51.55985161000513],[-3.171873580484352,51.55985161129316],[-3.1738272132818155,51.55930844518636],[-3.173827229004745,51.55930844046279],[-3.175765308341872,51.55874407227836],[-3.174521937192113,51.55712212925272],[-3.1726295125353747,51.557673198664894],[-3.1732283444941927,51.55849082581701],[-3.1712977234386623,51.5590275921508],[-3.170721897777361,51.558203568808906],[-3.1687996858085237,51.55871307098308],[-3.168799684465154,51.55871307075816],[-3.1668634581642183,51.55920155229187],[-3.1668634563262876,51.55920155375411],[-3.164913807683304,51.559668865172966],[-3.1644080544675472,51.55882715712456]]],[[[-3.1776872306425124,51.55815867800066],[-3.175765297008212,51.55874407527785],[-3.176387015620622,51.559555041716685],[-3.1783315864398873,51.55896274700958],[-3.1776872306425124,51.55815867800066]]],[[[-3.2000320725823137,51.55709382724969],[-3.198136930631612,51.55792283776885],[-3.1989548702871153,51.55866303985847],[-3.2008706272790906,51.55782500494681],[-3.2008706163489915,51.55782500950612],[-3.2027625471188435,51.556966230503505],[-3.2036215064741214,51.557688151267165],[-3.2055091269796714,51.55679942230632],[-3.205509111491231,51.55679942655129],[-3.207371488217715,51.55589026782799],[-3.207371483424782,51.555890272615116],[-3.209208048136593,51.55496096215377],[-3.208289656798926,51.55426808542017],[-3.210080583652866,51.55332901900716],[-3.210080571904217,51.5533290211972],[-3.2118448623555844,51.55237058610739],[-3.211844880907391,51.55237058305678],[-3.2135819968563166,51.55139307211077],[-3.212606695334128,51.5507311513552],[-3.2108882626960025,51.55169813740343],[-3.2099316545399166,51.55102568685924],[-3.208205318140036,51.55196348280748],[-3.2063301879074824,51.55059791203773],[-3.2046163203676192,51.551496534736614],[-3.204616308502422,51.55149654158696],[-3.20287747872931,51.552376353885776],[-3.2019787653766665,51.55167354475407],[-3.2002353264674896,51.55252459349175],[-3.2002353409664077,51.55252458951838],[-3.1984682784315814,51.55335651324434],[-3.1976094835270037,51.55263455403849],[-3.1958396922148014,51.55343785178594],[-3.199193534081846,51.556362647930364],[-3.2010447504844333,51.555522363848155],[-3.201903635204229,51.55624430036359],[-3.200032061769829,51.55709383175989],[-3.2000320725823137,51.55709382724969]]],[[[-3.2162851058055364,51.55104797129179],[-3.214557309881515,51.55205498588715],[-3.2155326678432785,51.55271689029286],[-3.217278847728127,51.55169914824838],[-3.2162851058055364,51.55104797129179]]],[[[-3.2242358357785084,51.556257149251586],[-3.22236096634793,51.55734999201216],[-3.2233365508298166,51.55801183094975],[-3.225229804740711,51.55690825829071],[-3.2242358357785084,51.556257149251586]]],[[[-3.1579429671798778,51.50713955752087],[-3.1574528410750666,51.507517626914556],[-3.1641242375557415,51.51094576644429],[-3.164709203826071,51.51049448909462],[-3.1635814349479996,51.509935356377376],[-3.164138217042385,51.50949014687166],[-3.1641382144470818,51.50949015057204],[-3.1646824493169543,51.50903894272327],[-3.1623658315591108,51.507970475106866],[-3.1618518226505055,51.50839660584992],[-3.162995007388291,51.50894338020941],[-3.1624536872548985,51.50937621758705],[-3.1613259671705047,51.508817067901745],[-3.1618518251017482,51.50839660235516],[-3.1607086701821383,51.507849813309406],[-3.1601982746941486,51.50825790732196],[-3.1590706098251107,51.50769873584815],[-3.159565542628834,51.50730301307263],[-3.1595655403216774,51.50730301636176],[-3.1600493221711825,51.50690196154755],[-3.15773292114859,51.50583340204918],[-3.1572793674555144,51.50620938211142],[-3.158422442441195,51.506756201645295],[-3.157942972562721,51.50713955348082],[-3.1579429671798778,51.50713955752087]]],[[[-3.2222623617024455,51.538995559796255],[-3.2208715570586417,51.54006954010166],[-3.2242118900396948,51.54178184359421],[-3.2256500799585397,51.54067120943819],[-3.2245208285150713,51.540112658740725],[-3.225912196725894,51.538998913182446],[-3.2259121902459498,51.53899892243674],[-3.2272721092957934,51.53787018782606],[-3.226112322998077,51.537336580733715],[-3.2247675018763498,51.53845276075415],[-3.2224782136060037,51.537360394789765],[-3.221133192676807,51.53843697672792],[-3.2222623617024455,51.538995559796255]]],[[[-3.235391372895592,51.54160511508746],[-3.23392577613357,51.54282174007046],[-3.2362156187526856,51.54391387269061],[-3.2377114066426778,51.542672133523844],[-3.235391372895592,51.54160511508746]]],[[[-3.1602308871064495,51.50517233600477],[-3.1597954677589173,51.505575632747615],[-3.1621702474286235,51.506593049873096],[-3.1626337460860237,51.506163724622844],[-3.1626337493042804,51.50616372175765],[-3.1630851620607427,51.50572941200305],[-3.161870046716434,51.50524688932846],[-3.161432306681141,51.50566803370902],[-3.1602308871064495,51.50517233600477]]],[[[-3.1602272275387517,51.50347906333856],[-3.159839176712434,51.50388257838651],[-3.1610675434426367,51.50435181446096],[-3.161468522085111,51.50393484472445],[-3.1602272275387517,51.50347906333856]]],[[[-3.160969167894193,51.502659658026644],[-3.160603931587152,51.5030713816113],[-3.161857775256123,51.50351356949982],[-3.1622351779694577,51.50308811777484],[-3.160969167894193,51.502659658026644]]],[[[-3.1626006142846927,51.502658621397266],[-3.1622351733718803,51.50308812011762],[-3.163501207043306,51.50351656624603],[-3.163878427509444,51.50307320867114],[-3.1626006142846927,51.502658621397266]]],[[[-3.1651562639240014,51.503487781995645],[-3.164767264459215,51.50394499867857],[-3.166033345619664,51.50437341741478],[-3.1664341235283735,51.50390234137034],[-3.1651562639240014,51.503487781995645]]],[[[-3.1642431839693224,51.50262581070815],[-3.1638784262893456,51.50307321060264],[-3.1651562626658287,51.50348778398752],[-3.1655324094163575,51.50302639876393],[-3.1642431839693224,51.50262581070815]]],[[[-3.160694839083666,51.50101497319185],[-3.160375645885254,51.501423958645226],[-3.16166480360146,51.501824589196026],[-3.1619949943172365,51.50140149718117],[-3.160694839083666,51.50101497319185]]],[[[-3.1632951715475635,51.50178800673379],[-3.1629539839247207,51.50222520555103],[-3.16424318685497,51.50262580770977],[-3.1645953707744883,51.50217450184926],[-3.1632951715475635,51.50178800673379]]],[[[-3.1623132960872633,51.50097487464951],[-3.161994992626022,51.50140149967593],[-3.163295169800029,51.50178800931173],[-3.163624073511288,51.50134715982034],[-3.1623132960872633,51.50097487464951]]],[[[-3.1599777244071006,51.4998289039286],[-3.159691807805289,51.50023025706542],[-3.1610025425985286,51.500602571462146],[-3.1612986609027027,51.50018688098989],[-3.1599777244071006,51.4998289039286]]],[[[-3.3027583965096503,51.53840426256749],[-3.3013745399229766,51.5403553582081],[-3.30268758036507,51.54072608570904],[-3.3040815666823473,51.538760628261834],[-3.3027583965096503,51.53840426256749]]],[[[-3.158921653923412,51.49908095309156],[-3.1586568079570414,51.49947091411423],[-3.159977723731473,51.499828906152075],[-3.160252369181798,51.49942449904946],[-3.158921653923412,51.49908095309156]]],[[[-3.2687436844045465,51.53502740619075],[-3.26745830644661,51.536608418463196],[-3.26873802114608,51.53702186043527],[-3.2700347407411856,51.53542684282408],[-3.2700347510038976,51.535426832133105],[-3.27128654796439,51.533817929766954],[-3.27389061230031,51.534588515541195],[-3.2751182019026257,51.53293773168437],[-3.273805619863184,51.532566681834176],[-3.27497659748533,51.5309171319013],[-3.2749765947315597,51.53091714103612],[-3.276101154207301,51.52925508467467],[-3.274768699423533,51.52891285542557],[-3.2736538793300562,51.53056044662301],[-3.2723311846020953,51.530203737276594],[-3.2734362646198134,51.52857061102427],[-3.2707714549538993,51.52788607676672],[-3.269685857167902,51.52949027378549],[-3.268363227097439,51.52913351090415],[-3.2672429784144525,51.53071127894285],[-3.268555454184679,51.53108240174832],[-3.2673806003226837,51.532661966269075],[-3.2686825650455855,51.53304729575056],[-3.2674526607512404,51.53462794482122],[-3.2687436844045465,51.53502740619075]]],[[[-3.286761511718752,51.53199237313936],[-3.285559062157717,51.53377015869122],[-3.2868819636085886,51.53412671868557],[-3.2880941463077504,51.53233446600028],[-3.286761511718752,51.53199237313936]]],[[[-3.282339769851544,51.54590099451363],[-3.2807481338155227,51.547628476819185],[-3.2832355539894578,51.54853746697633],[-3.2848521906956183,51.54678274608946],[-3.287364725697227,51.547664428210744],[-3.288956103292128,51.54586485369198],[-3.2902244922394654,51.546291937864446],[-3.2917768559564324,51.544461415781065],[-3.293057006217807,51.54487459989944],[-3.2945691050706922,51.54301343398335],[-3.2958606254524834,51.5434125732925],[-3.297331213521907,51.5415210851405],[-3.2986336700307493,51.54190610210752],[-3.3000615088652516,51.53998463157946],[-3.3000615208180744,51.53998461599066],[-3.3014352470043695,51.53804788193058],[-3.301435243777387,51.538047892654774],[-3.3027544451713697,51.53609648706523],[-3.3014215908578386,51.53575456094602],[-3.3001121149634187,51.53769149699625],[-3.297465922495812,51.53697865036868],[-3.2961225915287082,51.53887230104344],[-3.293496733847252,51.53813069918134],[-3.292121569663448,51.53998092821119],[-3.2908192227833903,51.53959583925269],[-3.2894033093718695,51.54141667195474],[-3.2894032977624104,51.541416684054305],[-3.287936524240404,51.54322181134924],[-3.2853764272064745,51.54239532544009],[-3.2838826878996628,51.544156429195105],[-3.2838827067262075,51.54415641957781],[-3.2823397863644983,51.54590097946175],[-3.282339769851544,51.54590099451363]]],[[[-3.3054202137168445,51.53678029382088],[-3.3040815634081446,51.53876063914497],[-3.305404754224371,51.539116989975795],[-3.306753127948101,51.53712217445649],[-3.3054202137168445,51.53678029382088]]],[[[-3.308086062151248,51.53746403993008],[-3.306727965707469,51.53947332586323],[-3.3080511978571447,51.539829646806815],[-3.309419016325949,51.537805890241245],[-3.308086062151248,51.53746403993008]]],[[[-3.2744953937011005,51.5269257017438],[-3.2734362543216085,51.52857061892512],[-3.274768689034347,51.52891286339646],[-3.2758371605508048,51.52725337735963],[-3.2744953937011005,51.5269257017438]]],[[[-3.155416628486768,51.50476479661559],[-3.154993304046964,51.50511570310275],[-3.15613632206936,51.50566254820164],[-3.156574761272865,51.50529910507404],[-3.157732925248159,51.50583339803345],[-3.158175887678878,51.50545253368156],[-3.157002964424766,51.50493093869297],[-3.1574207979972657,51.50455816484097],[-3.1586081175895906,51.50506690609026],[-3.159029496723894,51.50467662317555],[-3.1590294996496944,51.50467662057093],[-3.15943989303071,51.50428180609995],[-3.159439890900021,51.504281807325285],[-3.1598391737910094,51.50388258173263],[-3.158610835214177,51.50341332941146],[-3.1589859577584645,51.50302326878198],[-3.160227226746991,51.503479066599915],[-3.160603927680639,51.50307138516284],[-3.1593501121881995,51.50262918028719],[-3.159703181563312,51.50223118458352],[-3.160969163444904,51.5026596602938],[-3.161322824249777,51.50224402017449],[-3.1600450562988858,51.50182940675366],[-3.1603756432699788,51.50142396136242],[-3.1590865082510438,51.50102331652254],[-3.158767312681765,51.50141477757407],[-3.1574895922545183,51.50100013444694],[-3.157797395839332,51.50062265748773],[-3.1577973982743446,51.50062265495798],[-3.1580945946073458,51.500241881904834],[-3.1593947042684083,51.50062843709465],[-3.159691805318418,51.5002302602957],[-3.158381094372129,51.49985792799869],[-3.1586568086085984,51.499470911970164],[-3.157335912879987,51.499112907179665],[-3.1575909586743056,51.4987373920171],[-3.157590956301749,51.498737393832656],[-3.1578354591986932,51.49835915712683],[-3.1605156606748745,51.4990171598204],[-3.1607675168860516,51.498607015449075],[-3.158069346385353,51.49797831328281],[-3.1582925438301332,51.49759498005404],[-3.1569349121104673,51.49729535045286],[-3.156720289708442,51.49766393763989],[-3.1567202910151617,51.49766393538941],[-3.156495391821024,51.49803012805037],[-3.1564953889909524,51.498030131086765],[-3.156260281153461,51.4983938175722],[-3.1549296282052337,51.49805022452014],[-3.154694184819083,51.49839684862207],[-3.153373352390994,51.498038795173485],[-3.153138454244315,51.49836846504042],[-3.1531384522012225,51.49836846769376],[-3.152894368325094,51.498695528022225],[-3.152894369621999,51.49869552610967],[-3.15264117434072,51.49901987725355],[-3.153930194248324,51.49942059521838],[-3.153656570112191,51.499756121384905],[-3.156211896008705,51.50058547580345],[-3.1559053611803622,51.5009456840538],[-3.1559053650364817,51.500945682089096],[-3.155588799610212,51.50130249570601],[-3.154335074034434,51.50086024360366],[-3.1540211256341824,51.501199961575864],[-3.1540211262943156,51.5011999588581],[-3.1536977215357433,51.50153620451466],[-3.1536977191009776,51.50153620730296],[-3.1533649562096504,51.501868878109306],[-3.154579893662038,51.50235148813904],[-3.1542241984355126,51.50269364614494],[-3.1542241958996904,51.50269364840218],[-3.153858981338637,51.50303187884142],[-3.1526717669440645,51.5025230896963],[-3.152311548632829,51.50284443208696],[-3.152311550592343,51.502844430230255],[-3.1519423960769966,51.50316179899892],[-3.1519423926604646,51.50316180234519],[-3.151564414156095,51.503475100676305],[-3.151564415958862,51.50347509810679],[-3.151177728688971,51.503784230539374],[-3.1511777243478085,51.50378423379724],[-3.1507824458697677,51.50408910601467],[-3.1518941088778565,51.50466055264433],[-3.152305195811301,51.50434348164609],[-3.1534326995656787,51.5049027150857],[-3.1538503153346187,51.50456884404013],[-3.153850313387715,51.504568846815246],[-3.1542585227897093,51.50423047667431],[-3.155416628486768,51.50476479661559]]],[[[-3.2620024210568817,51.52213855399508],[-3.261078796358144,51.523648088164215],[-3.26242037067523,51.52397591727075],[-3.2633529242062793,51.52245171682724],[-3.2633529187892503,51.52245172619413],[-3.264242628199245,51.52091759777856],[-3.261524608347312,51.520320778811424],[-3.26065193112046,51.52182538478699],[-3.2620024210568817,51.52213855399508]]],[[[-3.2815904847046893,51.53270037955508],[-3.280368794854748,51.53442171703847],[-3.282994214237358,51.53516362815575],[-3.2842361842974785,51.53341357406869],[-3.284236181377879,51.53341358375975],[-3.285428897106764,51.53165026512254],[-3.28676150051088,51.53199238174066],[-3.287913928955226,51.530201766500134],[-3.2852300814872537,51.52954666176106],[-3.286314311825166,51.52777310353489],[-3.2863143055274207,51.52777311443412],[-3.2873486652327406,51.52598801374647],[-3.2887080109189024,51.526286119059456],[-3.289700275297806,51.52447516562285],[-3.2897002624831555,51.524475174712286],[-3.2906416332592063,51.5226536864853],[-3.2892665413867324,51.52238536486472],[-3.2883328381359846,51.52419191205087],[-3.2842306669091115,51.523342028360744],[-3.2851413627486368,51.52158030322861],[-3.282391324478929,51.5210435148293],[-3.281495970942445,51.52277535948264],[-3.2828633227306594,51.523058693180396],[-3.281911495865955,51.52479537243493],[-3.2791930073216915,51.524198975305275],[-3.2782096754670547,51.52589549110277],[-3.2782096814541166,51.52589548074442],[-3.277178959652655,51.527581023631264],[-3.277178946662848,51.52758103761204],[-3.2761011437271463,51.52925509271561],[-3.280098638435566,51.530281681504725],[-3.2789448649745263,51.53198713467135],[-3.2815904847046893,51.53270037955508]],[[-3.2838881661440684,51.529219108408704],[-3.282763716880123,51.53096601201408],[-3.28143116229213,51.53062385846206],[-3.2825462837263584,51.5288915249794],[-3.2838881661440684,51.529219108408704]]],[[[-3.2860025675394513,51.51980893448608],[-3.285141364043359,51.521580313408556],[-3.2865164074368343,51.52184868350224],[-3.287384860282032,51.520062282785354],[-3.2860025675394513,51.51980893448608]]],[[[-3.2998261851262782,51.52234168403544],[-3.2988925252857344,51.52426328851697],[-3.300267730291655,51.524531497309184],[-3.3012086312255535,51.522594869341305],[-3.2998261851262782,51.52234168403544]]],[[[-3.298740037225192,51.51802354418087],[-3.297927383786031,51.51993397472206],[-3.300705868931443,51.52041019445185],[-3.301531311585604,51.51846942021452],[-3.298740037225192,51.51802354418087]]],[[[-3.2611239218646495,51.5152458785346],[-3.2603933477278124,51.516746894120075],[-3.265892454111175,51.517821438784615],[-3.266652080087366,51.51626034720215],[-3.2666520829707575,51.51626033483755],[-3.2673678749495156,51.51469120476749],[-3.2673678737638094,51.51469121272886],[-3.2680396184406004,51.51311452298476],[-3.265249000674729,51.51266783815766],[-3.2658645191907816,51.51111454134679],[-3.2644632394560227,51.51090641554287],[-3.2638537112229593,51.5124444767583],[-3.2610631712837987,51.511997715420016],[-3.2604235094366545,51.51349856518573],[-3.2618123544272772,51.51373711994428],[-3.2611239246385173,51.51524586664569],[-3.2611239218646495,51.5152458785346]]],[[[-3.261782290136395,51.50576939968263],[-3.2613168154294665,51.50727903550136],[-3.264140709762045,51.507634061499814],[-3.2646155920945708,51.50609360544951],[-3.264615591071622,51.50609361476963],[-3.265047290251938,51.50454821928379],[-3.2636263633367606,51.50440158393494],[-3.2631989350757475,51.505931520349414],[-3.261782290136395,51.50576939968263]]],[[[-3.2511003822346183,51.49182355613856],[-3.2510262375164367,51.49321893745602],[-3.255342063134557,51.4933317646337],[-3.2554185710231347,51.49188934522664],[-3.256857970259989,51.49191124775148],[-3.256894484230039,51.49045252855515],[-3.25977397578457,51.490464881435955],[-3.259769516537936,51.48897461229418],[-3.255450553977867,51.489003100009754],[-3.255454736042246,51.49044630820914],[-3.249695757095923,51.490421307459],[-3.249660988936803,51.49180159883287],[-3.2511003822346183,51.49182355613856]]],[[[-3.2177067429274313,51.47111198012924],[-3.218215618534832,51.47214668406733],[-3.2195950156524917,51.471890277630294],[-3.219078736447359,51.4708405839976],[-3.2204507189268816,51.47056917795735],[-3.2198973885305064,51.469510362154736],[-3.2198973898134504,51.469510367466064],[-3.2193145829976872,51.46845774116718],[-3.2193145786499393,51.4684577338515],[-3.218702474376431,51.46741161732292],[-3.2187024779968447,51.46741162371029],[-3.218061264835978,51.46637233506837],[-3.2167230408170906,51.46670282211523],[-3.2160623785190308,51.46568520738048],[-3.2134048130146726,51.4663752130467],[-3.2140465511006617,51.46736376756705],[-3.2127082849433073,51.46769422548623],[-3.213313275381166,51.46867499017191],[-3.216007910378739,51.46804333172603],[-3.216602720232288,51.46905999379921],[-3.2166027244571946,51.46906000090892],[-3.217169060629998,51.47008298880829],[-3.2171690593834894,51.47008298364654],[-3.2177067480200696,51.471111986111154],[-3.2177067429274313,51.47111198012924]]],[[[-3.2166923059579635,51.46431550023382],[-3.217391137591496,51.46534018688284],[-3.2187198703305877,51.464995146427626],[-3.2180111713925696,51.463956035515864],[-3.2166923059579635,51.46431550023382]]],[[[-3.127220681712565,51.48310923625088],[-3.12740704690764,51.483156241332196],[-3.1285086318928195,51.48149539973283],[-3.1282756825053553,51.48143664550625],[-3.1282756771361355,51.48143664425897],[-3.128041121543797,51.481380437068886],[-3.1280411230002665,51.481380437372096],[-3.127805032661336,51.4813267942562],[-3.12732460018854,51.48217407419381],[-3.1275370848989104,51.48222235388296],[-3.1270330270700417,51.48306426786339],[-3.1272206774170526,51.48310923525304],[-3.127220681712565,51.48310923625088]]],[[[-3.1266541022088044,51.482980500919965],[-3.1268441509917104,51.48302135259427],[-3.1273246014053404,51.48217407481715],[-3.1271108000276113,51.48212811753109],[-3.1271108010926416,51.48212811764857],[-3.126895748130232,51.48208449675921],[-3.126462941769926,51.48294172618128],[-3.126654103155512,51.4829805010244],[-3.1266541022088044,51.482980500919965]]],[[[-3.126679510306219,51.48204322597594],[-3.1268957493672707,51.482084497189845],[-3.1273285396367143,51.48122726609247],[-3.1270882781079767,51.48118141004342],[-3.126679510306219,51.48204322597594]]],[[[-3.1240552161529616,51.488126948656664],[-3.124101811716042,51.48813870120224],[-3.1252036368293226,51.48647789206826],[-3.125110448546195,51.486454387827344],[-3.125110446398304,51.48645438732839],[-3.125016615351122,51.48643190199579],[-3.125016615933767,51.4864319021171],[-3.124922170716582,51.48641044251091],[-3.124922171257417,51.48641044278797],[-3.124827140637814,51.48639001544393],[-3.124827141111199,51.48639001549616],[-3.124731554024293,51.486370626635946],[-3.1247315545741277,51.48637062682735],[-3.124635440134551,51.48635228235482],[-3.12463543817174,51.48635228185335],[-3.1245388256317574,51.48633498738258],[-3.124538826128118,51.48633498740228],[-3.124441744925504,51.48631874817343],[-3.124441745486299,51.48631874833462],[-3.1243442251904723,51.486303569292446],[-3.1243442256947542,51.486303569309705],[-3.124246296000777,51.48628945507746],[-3.1239347165791984,51.4871669676258],[-3.124008165181713,51.487177553501176],[-3.1240081648034934,51.487177553488245],[-3.124081306333914,51.48718893798533],[-3.12408130591331,51.48718893786443],[-3.124154118098747,51.48720111751556],[-3.1241541177264707,51.48720111750077],[-3.1242265783882943,51.48721408859708],[-3.123817704170635,51.48807589403885],[-3.123865763028679,51.48808506661732],[-3.12386576275375,51.48808506652162],[-3.1239135578959045,51.488094761311444],[-3.123913557659204,51.488094761285325],[-3.1239610745262043,51.48810497533411],[-3.1239610742557775,51.488104975195576],[-3.124008298378167,51.48811570539204],[-3.1240082980868373,51.48811570533139],[-3.1240552150789793,51.48812694840718],[-3.1240552161529616,51.488126948656664]]],[[[-3.124147987362437,51.486276410078254],[-3.1242462966278497,51.4862894552036],[-3.124557864245129,51.485411941727044],[-3.1244349799197435,51.48539563563148],[-3.124434977181173,51.485395635289514],[-3.124311655835703,51.485380670557355],[-3.1240493262631155,51.48626443778971],[-3.1241479851715392,51.486276409804674],[-3.124147987362437,51.486276410078254]]],[[[-3.124187935788763,51.48536705172767],[-3.12431165658949,51.4853806705773],[-3.1245739761709466,51.484496902628266],[-3.1244255139950856,51.48448056032261],[-3.1244255148572724,51.48448056072178],[-3.124276618590134,51.484465838421904],[-3.124063853935745,51.485354783240716],[-3.124187936507263,51.4853670520603],[-3.124187935788763,51.48536705172767]]],[[[-3.12412733439418,51.48445274051045],[-3.1242766194270284,51.48446583832129],[-3.1244893759473116,51.48357689296424],[-3.1243152133992385,51.483561612480074],[-3.12412733439418,51.48445274051045]]],[[[-3.12144574114814,51.378349646508156],[-3.1245540676843024,51.37834964648502],[-3.1245665705444527,51.37745084860092],[-3.1214332382538204,51.37745084862426],[-3.12144574114814,51.378349646508156]]],[[[-3.121483252769591,51.38104603931995],[-3.1245165561653674,51.38104603929737],[-3.124541564334428,51.37924844422913],[-3.1214582445321852,51.37924844425207],[-3.12144575867378,51.378349644911665],[-3.118337902059592,51.37838351125475],[-3.118337920103634,51.37838351168502],[-3.115231473260233,51.3784512341009],[-3.115293971060167,51.37934921454815],[-3.118375426182295,51.379282036975624],[-3.1184504251372274,51.381079086685304],[-3.1214832698722277,51.38104603776208],[-3.121483252769591,51.38104603931995]]],[[[-3.1126711671806686,51.341633887919116],[-3.1168014341199717,51.341543845125585],[-3.1167639896314077,51.34064531351081],[-3.1126087719488087,51.34073590026034],[-3.112608681379624,51.3407359058947],[-3.1084565983548913,51.34087175308765],[-3.108456621805666,51.340871753931424],[-3.1043089258317167,51.34105281934212],[-3.104308949515919,51.34105281107548],[-3.1001668915635703,51.34127904007811],[-3.100303997666704,51.342173766264814],[-3.1044211831288044,51.34194889573757],[-3.1045333977190444,51.342844988321374],[-3.108631280692616,51.34266609749127],[-3.108631257523472,51.34266609665768],[-3.1127334753695917,51.34253188097279],[-3.1126711671806686,51.341633887919116]]],[[[-2.9932330810803878,51.35714478941464],[-2.9969524032471195,51.35575107825373],[-2.995499511881111,51.35420057643561],[-2.99173727296878,51.35561035285374],[-2.9924850740109914,51.35637761202057],[-2.9887837042043075,51.357819902361165],[-2.988014603974228,51.35706093898196],[-2.984332761267639,51.358551834111886],[-2.983542677771282,51.35780135099105],[-2.979881944956437,51.35934085811814],[-2.979881969878124,51.35934085224165],[-2.9762644607485997,51.360920011422685],[-2.976264481911527,51.36092000316339],[-2.972691293501515,51.36253833744891],[-2.9726912105851535,51.36253837130305],[-2.9691634281321537,51.36419539294728],[-2.9700351195738652,51.36490962960729],[-2.9735428818602103,51.363261997242105],[-2.97439466195351,51.36398558342994],[-2.977927299448752,51.36238558891622],[-2.978758727217343,51.363118380923645],[-2.98231466240526,51.3615660649882],[-2.9831255879489214,51.36230779690034],[-2.986703250076753,51.36080318148286],[-2.9874934373440905,51.361553642505875],[-2.991091261407319,51.360096729888255],[-2.9910912358574437,51.36009673768238],[-2.9947289001781923,51.35867924426098],[-2.9932330810803878,51.35714478941464]]],[[[-2.9640702956832086,51.36901307833985],[-2.9674650863378633,51.36729983760488],[-2.9665736087631736,51.36659521371145],[-2.9631593260783484,51.368318274579465],[-2.9640702956832086,51.36901307833985]]],[[[-2.962584016048248,51.37213256499907],[-2.9658923389338163,51.37040265926756],[-2.9649813143038686,51.36970786980878],[-2.961653772134447,51.37144780716936],[-2.962584016048248,51.37213256499907]]],[[[-2.9621719230741066,51.37592174962655],[-2.9653749360096198,51.374186781020164],[-2.963514309667972,51.37281730254093],[-2.9602734154314527,51.374572751696874],[-2.9621719230741066,51.37592174962655]]],[[[-2.961921399629071,51.379683487424586],[-2.9650198181756475,51.37794522033386],[-2.964070466246121,51.377270748751215],[-2.960953391495627,51.37901946200673],[-2.961921399629071,51.379683487424586]]],[[[-2.9746575209019532,51.39190814880265],[-2.9774133668159863,51.390306794010854],[-2.9745080819998395,51.38831508109612],[-2.971697135912124,51.389948393132016],[-2.9707104067581476,51.389295109525506],[-2.967927177670554,51.39096965043063],[-2.966922419709172,51.39032721519156],[-2.9641683539345673,51.39204287148552],[-2.963145847829086,51.39141147533395],[-2.960422393861821,51.39316811470588],[-2.962502382543585,51.39440841358897],[-2.965190915882577,51.39267424824834],[-2.967236082955731,51.39393699030029],[-2.969936845286839,51.39225447066495],[-2.970941688116028,51.39289687968951],[-2.9736707072479787,51.391254890632865],[-2.9746575209019532,51.39190814880265]]],[[[-2.9593824417023376,51.39254795132276],[-2.962123356024353,51.39078007539227],[-2.961100892366286,51.390148666461535],[-2.958342517662501,51.39192777864586],[-2.9562626828730916,51.39068744201291],[-2.9535198488422885,51.39251918731065],[-2.952462877596716,51.391910401347104],[-2.9497545100513345,51.39378336178626],[-2.9497545292767984,51.3937833473431],[-2.9470986020316405,51.395685532077906],[-2.9470986223848676,51.39568551964992],[-2.9444959460445777,51.397616352821835],[-2.942283030827053,51.39646949238513],[-2.9397027076546807,51.398452674921096],[-2.9430696734236426,51.40013650326433],[-2.945602458173801,51.39818974926378],[-2.94670898072334,51.398763159083735],[-2.9492792993500294,51.39685626356746],[-2.950369659607535,51.39744163241914],[-2.952976066299395,51.395574820420975],[-2.952976047432437,51.39557483459553],[-2.955633925337934,51.393736702051726],[-2.956690981065145,51.39434545904678],[-2.959382371969605,51.392547987238764],[-2.9593824417023376,51.39254795132276]]],[[[-2.9292139691585874,51.39662059443401],[-2.9318473569908985,51.39452341349778],[-2.93072528194345,51.39396204048137],[-2.9280763837328907,51.396071532411824],[-2.9292139691585874,51.39662059443401]]],[[[-2.9269388255437794,51.39552245929639],[-2.9296032343614926,51.39340065666823],[-2.9284812142443197,51.392839262058864],[-2.9258012945905985,51.394973375088234],[-2.9269388255437794,51.39552245929639]]],[[[-2.9231810825429063,51.39713635411543],[-2.9258013066967132,51.394973366436524],[-2.9235263266357134,51.393875164644065],[-2.920875764443249,51.39606310651341],[-2.9231810825429063,51.39713635411543]]],[[[-3.125254498581327,51.479105965771474],[-3.1255530344654354,51.47913215835774],[-3.140634707144864,51.41601552197319],[-3.138572626728376,51.415834604576794],[-3.1385726382770236,51.41583460434409],[-3.136505837197938,51.415676181314154],[-3.136505792349639,51.41567617908522],[-3.1344348971220772,51.415540299062165],[-3.1344349087905523,51.41554029883922],[-3.1323605472627487,51.41542700239891],[-3.1323605591232186,51.415427006504025],[-3.1302833602350826,51.4153363279887],[-3.130283371535149,51.415336327536046],[-3.1282039648265423,51.415268294439564],[-3.128203976800281,51.41526829189678],[-3.126122992539249,51.4152229248357],[-3.1261229475175045,51.41522292482951],[-3.1240410165807764,51.41520023817769],[-3.1240410283645694,51.415200239215054],[-3.121958781869967,51.41520023923056],[-3.12195879361026,51.4152002381619],[-3.119876862673529,51.4152229248452],[-3.119876874761115,51.41522292513303],[-3.1177958905001892,51.4152682916033],[-3.1177958451422767,51.41526829442246],[-3.115716438433659,51.41533632755282],[-3.115716450178164,51.41533632797399],[-3.113639251290017,51.415427006518364],[-3.113639263150494,51.41542700238415],[-3.111564901622675,51.415540298853514],[-3.111564913291158,51.415540299047336],[-3.1094940180635806,51.415676179099464],[-3.1094940301662612,51.41567617716982],[-3.1074272289636378,51.415834599608914],[-3.1074271845737136,51.41583460456686],[-3.105365104157215,51.41601552198263],[-3.105365115716465,51.41601552335671],[-3.1033083811457534,51.41621888029336],[-3.1033083929931182,51.41621887920043],[-3.1012576277532373,51.41644461400617],[-3.101257638409715,51.41644460920115],[-3.0992134641677795,51.416692653456366],[-3.099213475726119,51.41669265496109],[-3.0971765124593613,51.41696293345853],[-3.0971764685583665,51.41696293999578],[-3.095147334061782,51.41725537139855],[-3.0951473462285115,51.41725536656761],[-3.09312665550131,51.41756986160696],[-3.0931266652796574,51.41756986268177],[-3.091115031270625,51.41790632603569],[-3.0911150450517013,51.417906321164615],[-3.0891130776724505,51.418264650778966],[-3.08911308697269,51.418264648120136],[-3.0871213934729576,51.4186447354018],[-3.0871213517080593,51.41864474500889],[-3.085140536554773,51.41904647527057],[-3.0851405463640305,51.419046474151216],[-3.083171210132124,51.419469725021834],[-3.0831712212768916,51.419469723684166],[-3.0812139613937477,51.41991436681301],[-3.081213973344145,51.41991436426118],[-3.079269383598115,51.42038026481328],[-3.0792693924965606,51.42038026208393],[-3.077338062916258,51.42086727881666],[-3.0773380224537092,51.42086728756543],[-3.0754205394341643,51.421375273331236],[-3.0754205498524945,51.42137527196569],[-3.073517495045983,51.421904072161006],[-3.0735175081862667,51.42190406791827],[-3.0716294591217865,51.42245352214877],[-3.071629466730401,51.422453519402715],[-3.069756996569429,51.423023461094814],[-3.069757009279303,51.42302345717429],[-3.067900686447137,51.42361371341892],[-3.06790064484572,51.42361372491618],[-3.0660610334041394,51.424224117281646],[-3.0660610433730238,51.42422411393067],[-3.0642387015395993,51.424854456717156],[-3.064238714280074,51.42485445461979],[-3.0624341955420533,51.42550455660241],[-3.0624342066116594,51.42550455096456],[-3.060648058985591,51.42617421496565],[-3.0606480157676903,51.426174233331075],[-3.0588807825437945,51.42686325680069],[-3.058880794957667,51.426863253010424],[-3.057133012475169,51.4275714263189],[-3.0571330223640367,51.427571418079154],[-3.0554052216195404,51.428298526415524],[-3.0554052333855015,51.42829852363668],[-3.053697939664548,51.42904434638723],[-3.053697949653051,51.429044342485675],[-3.0520116819114853,51.42980865338735],[-3.0520116427791026,51.429808669378964],[-3.050346914262815,51.43059123706104],[-3.0503469277897004,51.430591233253125],[-3.048704244181021,51.43139181968462],[-3.0487042482921782,51.431391815385645],[-3.047084109193165,51.43221017773507],[-3.047084119490002,51.43221017526854],[-3.0454870178060482,51.43304606512715],[-3.0454870284643505,51.43304605889611],[-3.0439134500320995,51.433899222586405],[-3.0439134122056344,51.43389923864923],[-3.042363836535915,51.4347694177666],[-3.0423638512168827,51.43476941664929],[-3.040838749516608,51.43565634650927],[-3.0408387547027114,51.435656338447245],[-3.039338591108986,51.436559749945076],[-3.0393386031985026,51.43655974559252],[-3.03786383465766,51.43747936442673],[-3.0378638420753385,51.437479361637884],[-3.0364149178456876,51.438414908696146],[-3.0364148809756273,51.438414927707676],[-3.0349922431221192,51.43936611945337],[-3.034992251739705,51.43936611453737],[-3.0335963331542515,51.44033266168897],[-3.033596343061954,51.44033265423608],[-3.0322275691937666,51.441314263317444],[-3.032227579684973,51.44131425690289],[-3.0308863679214393,51.44231062985382],[-3.0308863723881836,51.44231062365645],[-3.0295731318055714,51.4433214579863],[-3.0295731031875968,51.443321482115664],[-3.0282882352934366,51.44434647121263],[-3.0282882410985428,51.444346467056775],[-3.0270321372292743,51.445385299254184],[-3.0270321472543156,51.445385292622234],[-3.0258051908026244,51.44643765236188],[-3.0258051975653206,51.44643764636004],[-3.0246077632351382,51.44750321403525],[-3.0246077682052017,51.44750320691961],[-3.0234402217623355,51.44858165887378],[-3.0234401993853686,51.448581683844374],[-3.0223028985229075,51.44967269275806],[-3.022302904244584,51.44967268485062],[-3.021196195730647,51.45077591896249],[-3.0211962019539875,51.45077591488364],[-3.0201204243555675,51.45189103899957],[-3.020120426819502,51.45189103005688],[-3.0190759091361627,51.45301770539241],[-3.019075891188453,51.45301773080048],[-3.0180629540903943,51.454155615213395],[-3.0180629591034247,51.45415560891052],[-3.017081911891035,51.45530435635415],[-3.0170819149184678,51.455304349047296],[-3.016133058122472,51.45646361042465],[-3.01613306494808,51.45646360486599],[-3.0152166894594186,51.45763302779519],[-3.015216694404381,51.4576330209767],[-3.014333081222332,51.45881225002235],[-3.0143330595855646,51.45881227513816],[-3.0134824807446403,51.46000095212131],[-3.013482489698333,51.460000945500404],[-3.012665205475953,51.46119870871937],[-3.012665205794152,51.46119870066399],[-3.011881467247098,51.46240518605901],[-3.011881472194137,51.462405180579346],[-3.0111315203508715,51.46362002126555],[-3.0111315250201267,51.46362001387722],[-3.010415590565719,51.464842840453535],[-3.0104155785044178,51.464842866822906],[-3.0097338828317057,51.466073307604944],[-3.0097338842714687,51.466073301396044],[-3.0090866365614155,51.4673109820245],[-3.0090866426277314,51.467310974829815],[-3.008474042513092,51.46855551881518],[-3.0084740473843605,51.46855551093809],[-3.0078962840144596,51.46980653973653],[-3.0078962853063205,51.46980653346273],[-3.00735353734302,51.471063666552844],[-3.007353521411277,51.471063693770986],[-3.006845957800468,51.472326548886606],[-3.006845965187137,51.4723265412106],[-3.006373742300394,51.47359473387181],[-3.006373746183654,51.4735947268637],[-3.005937010666882,51.47486787123229],[-3.005937008869724,51.47486786408041],[-3.005535896584233,51.47614557280319],[-3.0055358977872415,51.47614556506968],[-3.0051705337746655,51.47742744927776],[-3.0051705284593058,51.4774274773975],[-3.0048410279299205,51.47871314701716],[-3.0048410324988777,51.47871313975475],[-3.0045474977842517,51.48000220334877],[-3.004547497090924,51.48000219612674],[-3.004290020588123,51.48129426136236],[-3.004290024306097,51.48129425387635],[-3.0040686874492555,51.482588927368695],[-3.004068687765353,51.48258892015034],[-3.003883561019792,51.48388580777256],[-3.003883553435013,51.48388583592059],[-3.0037346972402053,51.48518454288193],[-3.0037347033193993,51.485184535505454],[-3.0036221651026596,51.486484666192965],[-3.0036221607115783,51.48648465897904],[-3.0035459778476437,51.48778581758106],[-3.003545978010562,51.48778581016181],[-3.003506176801297,51.48908760035641],[-3.0035061787624397,51.48908761544424],[-3.003502775067912,51.4903896407102],[-3.0035027732791586,51.49038964064854],[-3.0035357708225803,51.49169150441871],[-3.0035357709996906,51.491691504363885],[-3.0036051628391456,51.49299281002678],[-3.00360516736158,51.49299281032529],[-3.0037109354730274,51.49429316137981],[-3.0037109293236615,51.494293160938454],[-3.0038530445638196,51.49559216130446],[-3.003853045164186,51.49559216107593],[-3.004031467333393,51.49688941486157],[-3.0040314675320503,51.496889415346764],[-3.004246145367859,51.49818452723674],[-3.0042461490708487,51.49818452749319],[-3.004497020220342,51.49947710274897],[-3.0044970210026154,51.4994771027539],[-3.0047840120634914,51.50076674744688],[-3.0047840073699024,51.500766747956405],[-3.0051070339323958,51.50205306904591],[-3.005107039335554,51.502053068274506],[-3.005466005931943,51.50333567353044],[-3.0054660051987483,51.50333567357628],[-3.0058608054366367,51.50461417204554],[-3.0058608003601344,51.50461417263287],[-3.006291316887575,51.50588817456206],[-3.0062913201765196,51.50588817418422],[-3.0067574246779047,51.507157291020185],[-3.0067574242589723,51.50715729115655],[-3.0072589775733705,51.50842113593517],[-3.007258979308682,51.5084211353804],[-3.0077958313951174,51.50967932264959],[-3.081371449534458,51.496998041875486],[-3.0811767248711446,51.496543396530974],[-3.0811767242444095,51.49654339673188],[-3.0809947449550465,51.49608669905875],[-3.096397979335051,51.493783140011864],[-3.0962907398566815,51.493492700480715],[-3.101914080362989,51.492715690203255],[-3.101835815131602,51.49248551206047],[-3.1018358160485717,51.492485511953575],[-3.1017639987429204,51.49225451616002],[-3.1031797970081945,51.49209096802818],[-3.103118799411914,51.491874675220465],[-3.1031187985010185,51.49187467535135],[-3.103063857703113,51.49165775089169],[-3.1059119645798092,51.49139249383731],[-3.105870066885221,51.491206074841145],[-3.105870066772141,51.49120607484052],[-3.105833387473899,51.49101922774856],[-3.108694537592324,51.49081611420643],[-3.10872511326398,51.490971819377584],[-3.108725113358213,51.490971819378096],[-3.108760037604456,51.49112716769955],[-3.111608077108298,51.49086177244276],[-3.1116394944102934,51.490985727010774],[-3.111639494930782,51.490985726935946],[-3.1116743729914553,51.49110932031752],[-3.111674372920815,51.491109320322],[-3.111712701145277,51.49123251487277],[-3.111712700656247,51.49123251492982],[-3.111754467397699,51.49135527312477],[-3.1117544677145186,51.49135527308806],[-3.111799660274211,51.491477557521414],[-3.111799660233878,51.49147755753461],[-3.1118482648730543,51.4915993309573],[-3.1118482650401282,51.49159933090368],[-3.1119002669769795,51.49172055621594],[-3.110512746341928,51.49196061443526],[-3.11057504891895,51.49209633547246],[-3.1105750484162376,51.49209633533256],[-3.110641136291766,51.49223135689909],[-3.1106411364078665,51.492231357078396],[-3.1107109894902054,51.492365638032304],[-3.11071099008442,51.49236563808638],[-3.1107845871216306,51.492499137512475],[-3.110784586888694,51.49249913723908],[-3.1108619054820434,51.492631814474215],[-3.110861905790134,51.49263181463271],[-3.1109429224277108,51.49276362924896],[-3.1096031604256,51.49309290053731],[-3.109697256902608,51.49323835920736],[-3.109697256862891,51.49323835904037],[-3.10979540636556,51.4933827703486],[-3.1097954069781046,51.49338277036292],[-3.1098975796455006,51.49352609030313],[-3.1112079085031072,51.49315349727495],[-3.1113034596305646,51.49328146264061],[-3.11130345912602,51.493281462615506],[-3.111402573917977,51.49340836744138],[-3.111402573964561,51.49340836731591],[-3.1115052222494803,51.49353417293064],[-3.1115052226883684,51.49353417310656],[-3.111611373228463,51.49365884117511],[-3.1128768602848953,51.49323007999755],[-3.112974302799587,51.49333985011411],[-3.112974303155302,51.49333985020749],[-3.1130748002781,51.493448541940346],[-3.113074800066306,51.493448541927854],[-3.113178321196297,51.49355612216688],[-3.1131783212893143,51.49355612219807],[-3.1132848349077746,51.49366255816207],[-3.1132848350319966,51.49366255826035],[-3.113394308708371,51.49376781751948],[-3.1145950996523477,51.493271820937856],[-3.1144993087776727,51.493179720064546],[-3.114499308668979,51.49317971997857],[-3.1144061077355496,51.49308658947271],[-3.1168616048915623,51.49214748533402],[-3.1167969007038367,51.49208024971103],[-3.11679690083621,51.492080249718846],[-3.1167340864267246,51.49201231937334],[-3.116734086204407,51.49201231931499],[-3.116673180703253,51.4919437149384],[-3.116673181019585,51.491943715045835],[-3.116614202968545,51.491874457544526],[-3.1166142027247172,51.49187445744679],[-3.1165571700884738,51.49180456791406],[-3.116557170062607,51.49180456798377],[-3.1165021002025757,51.491734067713864],[-3.1152024732699193,51.49212093809565],[-3.115138766798828,51.49203562996132],[-3.1151387667803787,51.49203562990934],[-3.1150774552414426,51.491949640673624],[-3.1150774548739224,51.49194964066506],[-3.1150185569311946,51.49186299652107],[-3.115018556955029,51.49186299662126],[-3.114962090535108,51.491775723969475],[-3.1149620908807822,51.491775723883805],[-3.1149080731773053,51.49168784931534],[-3.113559369045039,51.492002519823544],[-3.113499227060375,51.491899327794904],[-3.1134992272415585,51.49189932800754],[-3.1134419796486874,51.49179549643892],[-3.113441979186519,51.491795496396854],[-3.1133876434133168,51.49169105691352],[-3.113387643323004,51.491691056774066],[-3.1133362359055834,51.49158604119741],[-3.113336236296582,51.49158604130621],[-3.113287772868802,51.49148048163407],[-3.1132877729424053,51.49148048160734],[-3.113242268251645,51.491374409994364],[-3.1132422681054606,51.491374410041296],[-3.1131997359916816,51.491267858798956],[-3.113199736026978,51.49126785878744],[-3.1131601894279872,51.49116086037748],[-3.1131601891507685,51.491160860409586],[-3.1131236400917794,51.491053447424335],[-3.114534568471928,51.490874362940716],[-3.114565898945464,51.490966430840615],[-3.1145658991830802,51.490966430813074],[-3.114599798932913,51.49105814333368],[-3.1145997989026633,51.491058143343565],[-3.114636257618237,51.49114947254907],[-3.1146362577435363,51.491149472508845],[-3.1146752643321047,51.491240390575435],[-3.1146752642690227,51.491240390598335],[-3.1147168068646294,51.491330869831025],[-3.114716806529482,51.49133086973777],[-3.114760872488459,51.49142088257601],[-3.1147608725658738,51.491420882695536],[-3.114807448484763,51.49151040171324],[-3.1175383362426947,51.490940164640065],[-3.1175710538262016,51.490999495834856],[-3.117571053722662,51.49099949571335],[-3.1176054251141325,51.491058461406894],[-3.117605425251064,51.49105846147733],[-3.1176414399915435,51.491117043704044],[-3.11764143976111,51.49111704376117],[-3.1176790868851403,51.49117522467885],[-3.1176790868692463,51.49117522461204],[-3.1177183549109473,51.491232986494325],[-3.1177183551559566,51.491232986500016],[-3.117759232160906,51.49129031174761],[-3.11775923217322,51.491290311782265],[-3.1178017056954928,51.49134718293423],[-3.1178017054712623,51.49134718292308],[-3.117845762576413,51.49140358265676],[-3.1178457625971046,51.49140358260099],[-3.1178913898681357,51.49145949373206],[-3.1178913900632073,51.49145949381023],[-3.1179385736095715,51.49151489930393],[-3.11920394226674,51.49108606981462],[-3.119240487138896,51.49112723166205],[-3.1192404872722816,51.491127231697064],[-3.119278177400735,51.491167989109314],[-3.11927817732131,51.49116798910463],[-3.1193170012262046,51.49120832966784],[-3.119317001261085,51.49120832967954],[-3.1193569471186553,51.49124824110321],[-3.119356947165248,51.491248241140056],[-3.1193980028100667,51.491287711265535],[-3.119398002592372,51.49128771120166],[-3.1194401555173656,51.49132672800767],[-3.1194401556090248,51.491326728064756],[-3.1194833929789296,51.491365279661885],[-3.1194833929989114,51.49136527960126],[-3.1195277016412715,51.49140335424592],[-3.1195277015387624,51.491403354165215],[-3.119573067957451,51.49144094025965],[-3.1195730680006672,51.4914409404321],[-3.1196194783848563,51.491478026524405],[-3.119619478650703,51.49147802643169],[-3.119666918856844,51.49151460122142],[-3.1196669187114066,51.491514601152396],[-3.1197153742902466,51.49155065350182],[-3.1197153741775185,51.49155065358689],[-3.1197648303740837,51.49158617251248],[-3.1197648305309906,51.49158617256426],[-3.119815272280064,51.49162114724241],[-3.119815272219276,51.49162114722941],[-3.1198666841544114,51.49165556700763],[-3.118822220206451,51.49227423749967],[-3.118892042087261,51.492319377299715],[-3.118892042084869,51.4923193771557],[-3.1189631154638815,51.492363749356045],[-3.1189631152331856,51.49236374935454],[-3.1190354184647964,51.49240734043922],[-3.1190354185585263,51.49240734047723],[-3.119108929619339,51.49245013716223],[-3.1191089299318473,51.49245013732781],[-3.1191836264326875,51.49249212657219],[-3.119183626200038,51.49249212627608],[-3.119259485373953,51.49253329529325],[-3.1192594852153874,51.49253329541183],[-3.1193364839655917,51.49257363165831],[-3.1193364838181816,51.49257363153351],[-3.1194145986820923,51.49261312272016],[-3.1194145988303035,51.49261312272527],[-3.119493806015502,51.49265175681731],[-3.1194938060435526,51.4926517570657],[-3.119574081427811,51.492689522293894],[-3.118717557241875,51.49341201201886],[-3.1188192069041807,51.49345811882246],[-3.1188192071713994,51.49345811884092],[-3.1189221306215957,51.4935031111242],[-3.11892213077774,51.49350311095173],[-3.1190262966550613,51.49354697500758],[-3.119026296488298,51.493546975048936],[-3.119131673064573,51.49358969751622],[-3.119131673162933,51.493589697564694],[-3.1192382283350644,51.49363126542365],[-3.1192382283662727,51.49363126545995],[-3.1193459296712,51.49367166604616],[-3.1193459298694917,51.49367166623256],[-3.119454744499219,51.4937108872347],[-3.119454744588944,51.493710887107966],[-3.1195646393830496,51.49374891657706],[-3.1195646387818687,51.49374891637956],[-3.1196755802574407,51.4937857427361],[-3.1196755804139746,51.49378574288598],[-3.1197875347806443,51.493821354901144],[-3.119787534883517,51.49382135491611],[-3.119900468028448,51.49385574173984],[-3.1199004682169633,51.49385574183391],[-3.120014345734844,51.493888892987954],[-3.120014345796753,51.493888892999905],[-3.120129132989802,51.4939207983848],[-3.1201291323575564,51.4939207980391],[-3.120244794243005,51.49395144794118],[-3.120244794327538,51.4939514481931],[-3.120361295682617,51.49398083326578],[-3.120361295797023,51.493980833083626],[-3.120478601114001,51.494008944373164],[-3.120478601255019,51.494008944477706],[-3.1205966748011584,51.4940357734185],[-3.121077356936687,51.493188522758075],[-3.1211724000151406,51.493208953114774],[-3.1211724000535948,51.493208953281524],[-3.1212679999374733,51.49322834501227],[-3.1212679993815695,51.49322834482833],[-3.1213641269346377,51.49324669203142],[-3.1213641271813346,51.49324669210249],[-3.121460753123396,51.493263989179184],[-3.121460753147872,51.493263989151046],[-3.1215578480367956,51.493280230833726],[-3.1215578481654704,51.49328023088495],[-3.1216553824211433,51.49329541222397],[-3.121655382517525,51.49329541221185],[-3.121753326421889,51.49330952858214],[-3.1214416516531287,51.494187033575315],[-3.1215645585541503,51.49420334276166],[-3.121564557860385,51.494203342570266],[-3.121687902103442,51.49421831018477],[-3.1216879022152155,51.49421831015312],[-3.1218116462225405,51.49423193162646],[-3.1218116463621195,51.494231931827194],[-3.121935752435945,51.49424420300862],[-3.121935752592857,51.49424420283161],[-3.1220601829079224,51.49425511998192],[-3.122060183069238,51.49425512001222],[-3.1221848997177637,51.49426467980495],[-3.1221848999030617,51.494264679946845],[-3.1223098648872596,51.49427287946887],[-3.1223098641294875,51.4942728794755],[-3.122435039369074,51.49427971623868],[-3.1224350395615157,51.49427971625714],[-3.1225603869161995,51.494285188167986],[-3.1225603870351266,51.49428518793466],[-3.122685868304622,51.494289293326695],[-3.1226858684402625,51.49428929349621],[-3.122811445401756,51.49429203111837],[-3.1228114455573626,51.494292031125894],[-3.1229370799430587,51.49429340014378],[-3.122937080091056,51.49429340007911],[-3.1230627336181556,51.49429340007568],[-3.123075299665205,51.495192179849106],[-3.1232260638914804,51.49519053699857],[-3.1232260640621203,51.49519053698647],[-3.1233767593739343,51.49518725177897],[-3.1233767595367055,51.49518725157242],[-3.123527340009098,51.495182325009125],[-3.123527340183998,51.49518232528613],[-3.123677759946,51.495175758868136],[-3.123677760112566,51.49517575884282],[-3.1238279733202545,51.49516755457104],[-3.123827973505155,51.49516755456552],[-3.1239779343866214,51.495157714939744],[-3.123977933514739,51.495157714771935],[-3.1241275963701107,51.49514624280201],[-3.1241275966280595,51.495146242762495],[-3.1242769158564836,51.49513314193181],[-3.1242769159160457,51.49513314214072],[-3.1244258460247103,51.49511841644344],[-3.124425846320939,51.495118416199055],[-3.1245743419161007,51.49510207011978],[-3.1245743420502308,51.495102070154935],[-3.124722357891162,51.495084108676345],[-3.124722357058632,51.4950841089032],[-3.124869848048671,51.4950645375089],[-3.1248698482619965,51.495064537354956],[-3.12501676944855,51.49504336199724],[-3.125016769593128,51.49504336201261],[-3.125163076212647,51.49502058914466],[-3.125163076405666,51.495020589065035],[-3.1253087238746753,51.49499622562553],[-3.1253087239113895,51.49499622566492],[-3.125453667853477,51.49497027907945],[-3.125453668223535,51.49497027897005],[-3.1255978644693543,51.494942757141246],[-3.1255978636354786,51.494942757414336],[-3.1257412682587535,51.49491366874151],[-3.1257412683119643,51.494913668810035],[-3.1258838376114952,51.49488302214711],[-3.125883837829968,51.49488302191983],[-3.1260255283566494,51.49485082660341],[-3.1260255285214047,51.4948508267966],[-3.1261662971141715,51.49481709263751],[-3.126166297246938,51.494817092531854],[-3.1263061010058304,51.49478182980955],[-3.1263061011027946,51.49478182982584],[-3.1264448974339194,51.49474504928561],[-3.1264448966706975,51.49474504937623],[-3.1265826432862336,51.49470676223474],[-3.1265826443948597,51.49470676190175],[-3.1267192993130783,51.49466697980503],[-3.126719298513948,51.49466698001642],[-3.1268548201131607,51.49462571510715],[-3.126854819459748,51.494625715403615],[-3.1269891664513594,51.494582980253774],[-3.1269891674070767,51.494582979745445],[-3.127122298835586,51.49453878735913],[-3.1271222981141578,51.494538787593335],[-3.1272541734380805,51.494493151454485],[-3.1272541745800817,51.494493151287905],[-3.1273847536090287,51.494446085281695],[-3.1273847525641747,51.49444608538089],[-3.1275139955218165,51.49439760387228],[-3.1275139965935863,51.49439760350725],[-3.127641864090634,51.49434772124559],[-3.1276418636982717,51.49434772151167],[-3.127768316795467,51.49429645371208],[-3.127768316075969,51.49429645398111],[-3.1278933162481177,51.494243816268316],[-3.127893316950373,51.49424381582961],[-3.1280168260953487,51.494189824226815],[-3.1280168253906937,51.494189824520525],[-3.1281388059013264,51.494134495494144],[-3.128138807192056,51.494134495303456],[-3.1282592218932486,51.49407784569029],[-3.128259220905525,51.4940778459606],[-3.1283780331261593,51.494019893046314],[-3.1283780328201214,51.49401989335168],[-3.1284952063660953,51.49396065478666],[-3.1284952071657632,51.49396065432536],[-3.128610706319991,51.493900148147176],[-3.1286107055627475,51.49390014828794],[-3.1287244951570745,51.493838392949314],[-3.128724496352791,51.49383839275335],[-3.1288365417140613,51.49377540704926],[-3.128836540642593,51.49377540712521],[-3.1289468076465905,51.49371121027145],[-3.1289468072633557,51.493711210848915],[-3.129055262334803,51.493645822397895],[-3.1290552630095196,51.49364582175164],[-3.1291618730771362,51.493579261614826],[-3.1291618725541883,51.4935792621495],[-3.129266605176505,51.49351155061986],[-3.129266606297555,51.493511549889185],[-3.1293694295370957,51.49344270757945],[-3.1293694289051435,51.49344270792971],[-3.129470311472254,51.493372755834095],[-3.1294703123054437,51.49337275541186],[-3.129569223450388,51.49330171482751],[-3.1295692227100878,51.49330171530306],[-3.1296661323309056,51.49322960789173],[-3.1307772662830464,51.49380125612128],[-3.1308879572393438,51.49371591076088],[-3.1308879584550082,51.493715909848895],[-3.130996245991365,51.49362937147631],[-3.1309962454863745,51.493629372195684],[-3.13110209666823,51.493541667196766],[-3.131102097625082,51.49354166625998],[-3.1312054801688616,51.4934528213412],[-3.131205479620071,51.493452821860984],[-3.1313063620819706,51.49336286410838],[-3.1313063610840217,51.493362864718726],[-3.1314047127445996,51.49327182154543],[-3.1314047134274867,51.493271820937856],[-3.1315005043021618,51.493179720064546],[-3.1302860557901324,51.49269686949911],[-3.1303659436138744,51.492617044181955],[-3.1303659441984473,51.49261704351289],[-3.1304435878222656,51.49253635995472],[-3.1304435876635246,51.49253636060695],[-3.130518963480923,51.492454843397326],[-3.13051896426252,51.49245484268721],[-3.1305920492832526,51.49237251665118],[-3.130592048393291,51.492372517104386],[-3.1306628203961147,51.49228940734175],[-3.1319400536039654,51.49270434362392],[-3.1320198952428404,51.49260649655179],[-3.132019895874395,51.49260649589606],[-3.132096989412792,51.492507793830356],[-3.1320969890178683,51.49250779441237],[-3.1321713110205427,51.49240826743529],[-3.1321713116426433,51.49240826662786],[-3.1322428394457518,51.4923079450505],[-3.132242839276533,51.492307945606335],[-3.132311551144845,51.49220686000134],[-3.1336418661546817,51.49255070821254],[-3.133563341052145,51.49266623549858],[-3.13356334124552,51.49266623486334],[-3.133481597830911,51.492780889004095],[-3.1347919053833837,51.493153496718946],[-3.1348838637477865,51.493024509787446],[-3.134883863530262,51.493024510502096],[-3.1349722011801,51.492894541314975],[-3.13497220035863,51.492894541943286],[-3.1350568904085847,51.49276363010494],[-3.135056891427805,51.49276362901202],[-3.1351379080586237,51.49263181438735],[-3.1351379075878807,51.492631815197434],[-3.135215226201446,51.49249913796229],[-3.1338578976846527,51.49219924019637],[-3.1339233203772734,51.49208057478569],[-3.1339233195355316,51.4920805753806],[-3.1339854143692376,51.49196121525934],[-3.133985414454673,51.49196121594963],[-3.134044162540925,51.49184119747102],[-3.134044162765368,51.491841196520134],[-3.1340995461869694,51.49172055624648],[-3.1340995460940184,51.491720556858795],[-3.1341515480488495,51.49159933154654],[-3.1341515482111935,51.49159933063588],[-3.1342001528413856,51.49147755721316],[-3.134200152799735,51.49147755794998],[-3.1342453453714127,51.49135527351664],[-3.1342453456909634,51.49135527248191],[-3.1342871124144396,51.49123251428697],[-3.1356980623618007,51.49141156551331],[-3.135741177493853,51.49127297112829],[-3.135741177399452,51.491272971967],[-3.1357804110652903,51.491133928942254],[-3.1357804116403933,51.49113392782105],[-3.1358157518685643,51.490994478507005],[-3.1358157513550378,51.49099447929506],[-3.1358471874687246,51.490854666184745],[-3.1358471875523706,51.490854665039194],[-3.135874709956992,51.49071453071773],[-3.1358747103629523,51.49071453147378],[-3.1358983107634977,51.490574118626675],[-3.1358983099937037,51.49057411947398],[-3.1359179812396425,51.490433470887325],[-3.1359179820606724,51.490433469762785],[-3.1359337181408367,51.490292628264136],[-3.1344966135856267,51.49023790729938],[-3.134507103037013,51.490112581657044],[-3.1345071034682146,51.490112580664395],[-3.134514087616378,51.48998715981824],[-3.1345140876328625,51.48998716051997],[-3.13451756442649,51.48986168267958],[-3.1345175642573606,51.48986168168179],[-3.134517532618775,51.4897361850713],[-3.1330778311076846,51.48974416122844],[-3.133077855332899,51.489853970763],[-3.13307785548086,51.48985397163607],[-3.1330748098299335,51.489963764709415],[-3.133074809815529,51.4899637640954],[-3.1330686952356412,51.49007350726145],[-3.1330686948583124,51.490073508130024],[-3.1330595131485657,51.490183167955486],[-3.1330595136749677,51.490183167356236],[-3.133047267651212,51.490292710446745],[-3.131611916530196,51.490222305189114],[-3.1315987936071643,51.49031607043624],[-3.1315987941204106,51.49031606987139],[-3.1315830518200567,51.490409677864434],[-3.1315830515494656,51.490409677360404],[-3.1315646946494082,51.49050309957601],[-3.1315646945935733,51.4905031003397],[-3.1315437286343406,51.49059630832165],[-3.131543728976736,51.490596307796295],[-3.131520160365129,51.49068927315546],[-3.1315201599816618,51.49068927390291],[-3.1314939958458696,51.49078196831163],[-3.1314939959088592,51.4907819677525],[-3.131465244240033,51.490874362978275],[-3.1314652446010762,51.49087436245858],[-3.1314339141410192,51.49096643035846],[-3.1314339139013088,51.49096643113451],[-3.1314000141424874,51.49105814365511],[-3.1314000141737592,51.49105814310249],[-3.131363555464926,51.491149472307995],[-3.1313635553431247,51.49114947299099],[-3.131324548741079,51.49124039105758],[-3.1313245488108117,51.491240390598335],[-3.131283006215205,51.491330869831025],[-3.131283006046837,51.49133087054418],[-3.13123894006539,51.491420883376804],[-3.1312389400013414,51.4914208828591],[-3.131192364077954,51.49151040187118],[-3.131192364709275,51.49151040142502],[-3.1311432930161827,51.49159939935298],[-3.1297860171035676,51.49129945586387],[-3.1297430547458567,51.49137316347247],[-3.1297430550074266,51.49137316302244],[-3.1296980384218838,51.491446391322356],[-3.129698037855602,51.49144639192949],[-3.129650980716819,51.49151911860329],[-3.129650981173223,51.49151911825425],[-3.129601897833889,51.49159132115735],[-3.1296018979547817,51.491591320760335],[-3.129550803359555,51.491662977892965],[-3.1295508029151797,51.4916629784697],[-3.1294977125932544,51.491734067995644],[-3.1281981073813707,51.491347183148505],[-3.128240580909636,51.49129031199655],[-3.128240581265155,51.49129031153518],[-3.128281458264121,51.49123298628385],[-3.1282814581673986,51.49123298660146],[-3.1283207262121016,51.49117522471919],[-3.1283207258469736,51.491175224998415],[-3.128358372979995,51.49111704408448],[-3.1283583734330414,51.49111704359877],[-3.12839438817052,51.49105846136829],[-3.1283943879612517,51.49105846172833],[-3.128428759361703,51.49099949603478],[-3.1297860174012495,51.491299455348404],[-3.1298269124260325,51.491225290879335],[-3.1298269118999236,51.49122529125113],[-3.1298657271684953,51.491150692523945],[-3.129865727221853,51.49115069295537],[-3.129902450921579,51.49107568268919],[-3.1299024510619082,51.4910756820949],[-3.1299370719870403,51.491000283139606],[-3.1299370719289157,51.4910002835223],[-3.1299695795704747,51.490924518849184],[-3.1299695796719935,51.49092451828002],[-3.129999964110984,51.4908484109678],[-3.1299999640849108,51.4908484114283],[-3.1300282161046864,51.49077198466303],[-3.1300282163044644,51.49077198401633],[-3.1300543272783523,51.49069526107742],[-3.130054326977472,51.490695261510496],[-3.130078288992329,51.49061826577544],[-3.130078288939822,51.49061826624139],[-3.13010009470803,51.490541021162265],[-3.1301000950276054,51.4905410205394],[-3.1301197378870373,51.490463549643],[-3.1301197376016945,51.4904635500808],[-3.1301372116093296,51.4903858769732],[-3.1301372116558794,51.490385876336774],[-3.1301525114698237,51.490308024675336],[-3.1301525116953,51.49030802509537],[-3.130165632695526,51.49023001859337],[-3.130165632267805,51.490230019064086],[-3.1301765704700175,51.490151881490796],[-3.1301765709262206,51.49015188086608],[-3.130185322979023,51.49007363601394],[-3.130185322602996,51.49007363644198],[-3.1301918858810254,51.489995308154725],[-3.1301918861505844,51.489995307534315],[-3.1301962586365333,51.489916919664864],[-3.130196258646795,51.48991692010345],[-3.130198439049585,51.48983849653259],[-3.130198438943938,51.48983849590897],[-3.1301984265809963,51.489760060526706],[-3.1244396115632647,51.48979164714818],[-3.12443917252162,51.489775962465956],[-3.124439172517993,51.48977596255368],[-3.124438295065118,51.48976028504374],[-3.124438295009592,51.489760284919655],[-3.1244369794073057,51.489744619358085],[-3.1244369794808886,51.48974461944704],[-3.1244352261363377,51.4897289706043],[-3.1244352261292017,51.489728970479824],[-3.1244330355709433,51.48971334312357],[-3.124433035569101,51.489713343211484],[-3.124430408470781,51.489697742101875],[-3.1244304084241104,51.489697742192575],[-3.1244273455865,51.48968217208231],[-3.1244273455755724,51.48968217194833],[-3.1244238479258053,51.48966663757979],[-3.124423847981204,51.48966663768074],[-3.12441991659169,51.489651143785075],[-3.1244199165258815,51.48965114366051],[-3.1244155525882937,51.48963569495861],[-3.1244155525971236,51.4896356950384],[-3.124410757446895,51.48962029623522],[-3.124410757422768,51.48962029610708],[-3.124405532515161,51.48960495189358],[-3.124405532562313,51.48960495199145],[-3.124399879495614,51.48958966704078],[-3.1243998794991574,51.4895896671195],[-3.124393799995213,51.489574446088],[-3.124393799973386,51.489574445974156],[-3.124387295879076,51.4895592934985],[-3.12438729588922,51.48955929357505],[-3.1243803691914827,51.48954421427043],[-3.1243803691618965,51.489544214151586],[-3.124373021965582,51.48952921261166],[-3.1243730220392476,51.48952921269841],[-3.1243652565885087,51.48951429349162],[-3.124365256606431,51.48951429356644],[-3.1243570752667114,51.489499461237784],[-3.124357075205663,51.4894994611347],[-3.1243484804644748,51.48948472020306],[-3.124348480515306,51.48948472029307],[-3.1243394749964275,51.489470075247354],[-3.1243394748816535,51.489470075125915],[-3.1243300613226945,51.48945553042887],[-3.1243300614124894,51.48945553049867],[-3.1243202426865775,51.48944109057842],[-3.1243202427092673,51.489441090657806],[-3.1243100218078457,51.48942675991351],[-3.124310021803276,51.48942675979863],[-3.1242994018346084,51.48941254259576],[-3.1242994018895476,51.48941254267984],[-3.1242883860944133,51.48939844334835],[-3.1242883860030517,51.489398443228836],[-3.1242769777315744,51.48938446606499],[-3.124276977768623,51.48938446615306],[-3.1242651805019372,51.48937061541296],[-3.125530430505242,51.4889417174087],[-3.125506064634325,51.48891427747656],[-3.1255060647185324,51.488914277666574],[-3.1254809354972255,51.48888710738352],[-3.125480935718057,51.4888871075483],[-3.1254550508011247,51.48886021518863],[-3.1254550506032315,51.488860214967495],[-3.1254284178655145,51.48883360872698],[-3.1254284180045855,51.48883360881049],[-3.125401045567996,51.488807296789474],[-3.1254010453698684,51.488807296617765],[-3.125372941565922,51.48878128683433],[-3.1253729416764466,51.48878128700964],[-3.1253441150762167,51.488755587382734],[-3.125344115230031,51.48875558753122],[-3.1253145746142312,51.48873020588816],[-3.1253145743378177,51.488730205620534],[-3.125284328693722,51.488705149696976],[-3.125284329006665,51.488705149903424],[-3.125253387561146,51.4886804273247],[-3.125253387210792,51.488680427066036],[-3.125221759376367,51.488656045369574],[-3.125221759646447,51.48865604552367],[-3.1251894550691706,51.488632012129074],[-3.1251894548025536,51.488632011965215],[-3.125156483314143,51.4886083341986],[-3.125156483382656,51.48860833427029],[-3.1251228550334047,51.488585019339936],[-3.125122855242561,51.48858501956383],[-3.1250885802803863,51.48856207456956],[-3.1250885800753316,51.48856207432696],[-3.1250536689278223,51.488539506261986],[-3.1250536690991515,51.48853950644209],[-3.1250181324124564,51.48851732217622],[-3.1250181321845494,51.48851732196269],[-3.124981980772492,51.48849552825728],[-3.1249819808987533,51.48849552834449],[-3.124945225781759,51.48847413183371],[-3.124945225965769,51.48847413196709],[-3.1249078783409336,51.48845313916729],[-3.124907878112511,51.48845313899578],[-3.124869949347572,51.48843255630552],[-3.124869949088089,51.48843255613721],[-3.12483145073642,51.48841238982602],[-3.124831450814625,51.48841238999183],[-3.124792394612257,51.488392646198314],[-3.124792394882902,51.48839264630104],[-3.124752792724534,51.48837333103668],[-3.1247527930507464,51.488373331128635],[-3.124712656998766,51.48835445027587],[-3.124712657077449,51.48835445043128],[-3.124671999358888,51.48833600974352],[-3.124671998562511,51.48833600930758],[-3.124630831545125,51.48831801441108],[-3.124630831828921,51.4883180144782],[-3.1245891680661027,51.488300470842155],[-3.12458916830592,51.48830047093369],[-3.1245470204887913,51.48828338390197],[-3.1245470206195693,51.48828338399254],[-3.1245044015859444,51.488266758770685],[-3.1245044019122172,51.4882667588689],[-3.1244613246436073,51.48825060051846],[-3.124461323703188,51.48825060023246],[-3.1244178013098574,51.48823491368874],[-3.124417801531413,51.48823491373896],[-3.124373847279317,51.48821970376716],[-3.124373847519768,51.48821970384708],[-3.1243294747975976,51.48820497507959],[-3.124329475076731,51.48820497512957],[-3.124284697398918,51.488190732052],[-3.1242846964138558,51.48819073183126],[-3.124239527411601,51.48817697879553],[-3.124239527680935,51.488176978861325],[-3.12419398112015,51.48816372004267],[-3.1241939813513246,51.48816372014457],[-3.124148071105708,51.48815095958171],[-3.1241480713570873,51.488150959613776],[-3.1241018114081096,51.48813870119308],[-3.1235508691364635,51.48896910171013],[-3.123527570999531,51.488963225331055],[-3.1235275704625316,51.488963225206305],[-3.1235041115993027,51.488957603566035],[-3.12350411174497,51.48895760359636],[-3.1234804993053054,51.48895223839978],[-3.123480499440527,51.48895223846904],[-3.1234567406177036,51.48894713135045],[-3.1234567407360534,51.488947131363524],[-3.123432842765284,51.48894228387867],[-3.1234328429027505,51.48894228392651],[-3.123408813064142,51.48893769755173],[-3.123408812573412,51.48893769742636],[-3.1233846581815317,51.48893337356544],[-3.1233846583056275,51.488933373570376],[-3.12336038672145,51.48892931353366],[-3.1233603868616564,51.488929313573955],[-3.1233360054790955,51.48892551859794],[-3.1233360056051733,51.48892551860225],[-3.123311521849593,51.48892198984298],[-3.123311522006368,51.48892198987452],[-3.12328694333607,51.488918728406496],[-3.1232869427883165,51.48891872833809],[-3.1232622766870572,51.48891573516241],[-3.1232622768378233,51.48891573516641],[-3.1232375308207985,51.48891301118717],[-3.123237530964507,51.4889130112537],[-3.123212712570872,51.48891055730067],[-3.12321271271037,51.4889105572839],[-3.123187829497243,51.48890837410459],[-3.123187829636609,51.48890837410181],[-3.12316288918358,51.48890646236118],[-3.1231628886423812,51.488906462334256],[-3.1231378985455005,51.48890482262204],[-3.1231378986863114,51.48890482261937],[-3.1231128665581007,51.48890345542756],[-3.123112866701217,51.488903455477036],[-3.1230878001676747,51.48890236122206],[-3.1230878003040394,51.488902361216624],[-3.12306270699865,51.48890154023184],[-3.123062707143147,51.488901540201226],[-3.123037594708379,51.488900992736625],[-3.123037594165074,51.48890099273654],[-3.123012470250881,51.48890071896638],[-3.123012470393083,51.488900718978876],[-3.1229873426524155,51.48890071897907],[-3.1229873427940924,51.488900718966185],[-3.1229622188798998,51.48890099273674],[-3.122962219025768,51.4889009927402],[-3.1229371065909985,51.48890154019765],[-3.122937106043642,51.48890154023165],[-3.1229120127382517,51.488902361216816],[-3.1229120128799805,51.48890236122189],[-3.1228869463464375,51.488903455477214],[-3.1228869464895554,51.4889034554274],[-3.1228619143613434,51.488904822619546],[-3.122861914502153,51.488904822621855],[-3.122836924405274,51.488906462334434],[-3.1228369245513155,51.48890646231117],[-3.1228119840967885,51.48890837404467],[-3.1228119835611365,51.48890837410447],[-3.1227871003480128,51.48891055728402],[-3.122787100487509,51.488910557300564],[-3.1227622820938747,51.48891301125382],[-3.1227622822368337,51.488913011240626],[-3.1227375362213055,51.4889157352201],[-3.1227375363498737,51.48891573516218],[-3.1227128702486096,51.48891872833832],[-3.122712870388098,51.488918728356424],[-3.122688291716301,51.488921989817435],[-3.1226882911865865,51.48892198989632],[-3.122663807432506,51.48892551865606],[-3.1226638075792863,51.48892551859782],[-3.1226394261967267,51.48892931357407],[-3.122639426314731,51.488929313587],[-3.1226151547320495,51.48893337362418],[-3.1226151548983028,51.48893337356544],[-3.1225910005064224,51.48893769742636],[-3.1225910006186224,51.48893769739429],[-3.122566970775521,51.48894228376254],[-3.122566970271648,51.48894228387845],[-3.12254307230087,51.488947131363744],[-3.1225430724192216,51.488947131350216],[-3.122519313596409,51.48895223846928],[-3.122519313730869,51.488952238453116],[-3.1224957012927024,51.48895760365016],[-3.1224957014368746,51.48895760361936],[-3.122472242575141,51.4889632252601],[-3.1224722426824836,51.48896322522718],[-3.122448944542552,51.48896910159971],[-3.1224489440544274,51.4889691017053],[-3.1224258137367444,51.488975231026195],[-3.1224258138624332,51.48897523100968],[-3.122402858409035,51.488981611405286],[-3.1224028585675305,51.488981611354106],[-3.122380084969561,51.488988240880644],[-3.1223800850613252,51.48898824084751],[-3.122357500256015,51.48899511748641],[-3.1223575004093154,51.48899511743911],[-3.1223351112755107,51.48900223909514],[-3.12233511077374,51.4890022392339],[-3.122312924136371,51.48900960374495],[-3.1223129242565935,51.48900960370452],[-3.12229094686873,51.48901720882065],[-3.122290947022416,51.48901720879531],[-3.1222691855787126,51.489025052199665],[-3.1222691857121783,51.489025052131666],[-3.122247646839933,51.48903313143545],[-3.1222476463188173,51.48903313165698],[-3.1222263365869103,51.48904144440637],[-3.1222263367366097,51.48904144436061],[-3.122205262628585,51.48904998801624],[-3.1222052627477423,51.4890499879169],[-3.122184430681242,51.489058759876805],[-3.122184430823138,51.48905875984323],[-3.1221638471454587,51.489067757435265],[-3.122163847265887,51.48906775738819],[-3.122143518246431,51.489076977871974],[-3.122143517774642,51.48907697806493],[-3.122123449612447,51.48908641863839],[-3.1221234497755566,51.48908641859241],[-3.1221036485768354,51.48909607637305],[-3.122103648626345,51.48909607632122],[-3.1220841204224237,51.48910594836858],[-3.1220841205465897,51.489105948338775],[-3.1220648712864394,51.489116031645054],[-3.122064871414881,51.4891160315699],[-3.122045906960638,51.48912632306374],[-3.1220459065046793,51.489126323257565],[-3.122027232640593,51.48913681981058],[-3.1220272328176635,51.48913681979699],[-3.122008855226179,51.48914751820502],[-3.122008855288558,51.48914751810784],[-3.1219907795652677,51.489158415113465],[-3.1219907797110027,51.48915841506093],[-3.1219730113631385,51.48916950734406],[-3.1219730114525452,51.489169507310386],[-3.1219555558930097,51.48918079149298],[-3.1219555554487277,51.489180791722355],[-3.121938418002092,51.48919226437307],[-3.1219384181059193,51.48919226431376],[-3.1219216039844078,51.48920392193204],[-3.1219216041037243,51.48920392184215],[-3.121905118428255,51.48921576087698],[-3.1219051185546345,51.48921576079961],[-3.121888966347265,51.48922777764477],[-3.1218889664009946,51.48922777757007],[-3.121873152581974,51.4892399685661],[-3.121873152237423,51.489239968857085],[-3.121857681634165,51.48925233029492],[-3.1218576817040673,51.48925233024479],[-3.1218425590196723,51.489264858354574],[-3.114899042399683,51.48605677058288],[-3.1150049069068424,51.4859690799918],[-3.1150049064174685,51.48596908034264],[-3.1151132056571416,51.48588255651336],[-3.1151132080691384,51.4858825544766],[-3.115223909113405,51.48579722379034],[-3.1152239087373,51.48579722431327],[-3.1153369777891164,51.48571311272574],[-3.1153369769044397,51.485713113267344],[-3.1154523795177087,51.48563024638728],[-3.1154523786824257,51.48563024701642],[-3.115570079699158,51.48554865007947],[-3.1155700789723673,51.485548650494565],[-3.1156900425435845,51.48546834835015],[-3.115690045653527,51.48546834674469],[-3.1158122352917577,51.48538936388878],[-3.1158122346659223,51.485389364124394],[-3.115936613098408,51.4853117245686],[-3.1159366120783356,51.485311724936345],[-3.116063141414769,51.48523545231917],[-3.116063140978034,51.485235452999376],[-3.1161917826686194,51.48516057055358],[-3.116191781429252,51.48516057064859],[-3.1163224963066476,51.48508710116837],[-3.116322499498258,51.485087099811714],[-3.1164552477880236,51.485015065718244],[-3.1164552468889246,51.485015066244294],[-3.1165899881047787,51.48494448942864],[-3.116589987235687,51.48494448963717],[-3.1167266803471296,51.484875391591174],[-3.1167266800005384,51.48487539195394],[-3.1168652833700126,51.484807793724464],[-3.116865282228303,51.48480779404626],[-3.117005753642602,51.48474171620896],[-3.117005756944968,51.484741714858444],[-3.117148053667636,51.48467717758325],[-3.117148052824668,51.484677177912744],[-3.1172921314702635,51.48461420080681],[-3.117292130477054,51.484614201041694],[-3.117437947168326,51.484552803280316],[-3.1174379463342023,51.48455280397567],[-3.117585456649667,51.48449300425642],[-3.117585455601803,51.484493004576585],[-3.1177346146281644,51.48443482110633],[-3.1177346182757577,51.48443481955567],[-3.1178853806393034,51.48437827010282],[-3.1178853797050965,51.48437827057874],[-3.1180376994355057,51.484323372313035],[-3.1180376983598133,51.48432337249039],[-3.1181915290787177,51.48427014212675],[-3.11819152823719,51.48427014240975],[-3.118346823095329,51.484218596159366],[-3.118346826607462,51.48421859518821],[-3.1185035383475244,51.484168748794445],[-3.1185035372745262,51.484168749125516],[-3.1186616181368407,51.48412061771664],[-3.1186616174945225,51.484120617948484],[-3.1188210193427826,51.484074216185014],[-3.118821018233411,51.484074216543256],[-3.1189816925232083,51.484029558549175],[-3.118981691643459,51.484029558664666],[-3.1191435894537407,51.48398665804059],[-3.1191435928702815,51.48398665730153],[-3.119306664924268,51.48394552715738],[-3.1193066641729534,51.48394552738782],[-3.119470860785721,51.48390618020213],[-3.1194708597766274,51.48390618041769],[-3.1196361309494245,51.48386862816857],[-3.1196361300082724,51.48386862828167],[-3.1198024254179884,51.48383288240416],[-3.1198024245896043,51.48383288249881],[-3.1199696935992725,51.483798953878456],[-3.1199696971260473,51.483798953067236],[-3.120137888826695,51.48376685208254],[-3.120137888041364,51.48376685230698],[-3.1203069511891406,51.48373658868549],[-3.1203069500254714,51.483736589096594],[-3.120476833135944,51.483708172049056],[-3.120476832310014,51.483708171958604],[-3.120647483668609,51.483681610141346],[-3.120647482641239,51.483681610549],[-3.1208188502717844,51.48365691204752],[-3.120818853979392,51.48365691149545],[-3.1209908857279394,51.483634083881796],[-3.1209908847516514,51.48363408375507],[-3.121163528223562,51.48361313392904],[-3.1211635273236804,51.483613134334426],[-3.121336729935994,51.483594068675735],[-3.1213367289353835,51.48359406876808],[-3.1215104379587273,51.4835768930808],[-3.121510436982372,51.48357689296506],[-3.121684599530442,51.48356161247924],[-3.121684603279583,51.48356161206066],[-3.121859166330076,51.48354823148058],[-3.121859165307898,51.483548231643425],[-3.1220340756920075,51.483536754991306],[-3.1220340747064514,51.483536754975056],[-3.1222092791717224,51.483527185745764],[-3.122209278170016,51.483527186094506],[-3.122384723359348,51.48351952720151],[-3.1223847223673653,51.483519527166045],[-3.122560354871392,51.483513780941415],[-3.122560358702441,51.4835137807035],[-3.1227361250490158,51.48350994894947],[-3.1227361240280573,51.483509948925224],[-3.1229119706927086,51.483508032755665],[-3.1229119697010863,51.4835080328458],[-3.123087843138425,51.483508032844504],[-3.1230878421431285,51.483508032757],[-3.1232636888077794,51.48350994892389],[-3.1232636926104633,51.48350994892451],[-3.123439458957039,51.48351378072844],[-3.1234394579456763,51.48351378094284],[-3.1236150904497015,51.48351952716461],[-3.1236150894952583,51.48351952720273],[-3.1237905346845896,51.48352718609327],[-3.123790533682885,51.483527185746986],[-3.1239657381481547,51.48353675497382],[-3.123965737162599,51.48353675499254],[-3.1241406475467084,51.483548231642196],[-3.1241406513346575,51.48354823183058],[-3.124315214374671,51.48356161246057],[-3.1246909494391693,51.48177935510791],[-3.124914864169476,51.481799000685804],[-3.1251275958725198,51.48091005376449],[-3.1248788064750714,51.48088822576617],[-3.124878807868464,51.48088822573829],[-3.124629446473458,51.48086911166267],[-3.1246294410624156,51.48086911139358],[-3.124379583577844,51.48085271713379],[-3.1246554541975105,51.47906335706372],[-3.124955271586159,51.47908302941233],[-3.124955278079158,51.479083029735236],[-3.1252545002533303,51.479105965738015],[-3.125254498581327,51.479105965771474]],[[-3.11603023341062,51.490924518480924],[-3.1159998489660112,51.49084841116869],[-3.115999848991216,51.49084841116045],[-3.11597159697893,51.49077198439516],[-3.1159715967809234,51.49077198441811],[-3.1159454857958044,51.4906952614792],[-3.115945486101434,51.49069526144354],[-3.1159215240884524,51.49061826570848],[-3.115921524132602,51.49061826570568],[-3.1158997183793784,51.49054102062656],[-3.11589971805408,51.49054102067334],[-3.1158800751909057,51.49046354977693],[-3.1158800754735125,51.490463549745975],[-3.1158626014752366,51.49038587663837],[-3.115862601428121,51.49038587663812],[-3.1158473016057537,51.49030802497668],[-3.115847301382682,51.49030802496144],[-3.1158341803862024,51.49023001845946],[-3.115834180374166,51.49023001843024],[-3.11582324218974,51.49015188085226],[-3.115823242153614,51.49015188086608],[-3.1158144901008114,51.49007363601394],[-3.1158144904712857,51.49007363604018],[-3.1158079272044894,51.48999530775293],[-3.1158079269320273,51.489995307735214],[-3.1158035544404608,51.48991691986576],[-3.1158013740335626,51.489838496298226],[-3.1172410815163127,51.48983073228516],[-3.117242827808433,51.4898934711206],[-3.1172463277817557,51.48995618137096],[-3.117246327999725,51.48995618138512],[-3.117251580578711,51.49001884395108],[-3.117251580282332,51.49001884393008],[-3.1172585838817364,51.490081439726744],[-3.1172585839106377,51.4900814397157],[-3.1172673364045687,51.49014394967192],[-3.1172673364141996,51.490143949695295],[-3.1172778351446326,51.49020635476976],[-3.11727783532309,51.49020635478194],[-3.117290077098736,51.49026863596315],[-3.1172900771364276,51.490268635963346],[-3.1173040582349643,51.49033077428085],[-3.1173040580088793,51.490330774305626],[-3.117319774179185,51.49039275083372],[-3.1173197744394208,51.4903927507963],[-3.1173372208993286,51.49045454665038],[-3.117337220864006,51.49045454665262],[-3.1173563923070198,51.490516143011504],[-3.1173563920625176,51.49051614304003],[-3.1173772826562978,51.49057752114234],[-3.1173772828147,51.49057752112397],[-3.117399886201081,51.49063866226802],[-3.117399886180915,51.490638662274606],[-3.1174241954818522,51.490699547837146],[-3.11603023341062,51.490924518480924]],[[-3.1213468992265154,51.49229067669012],[-3.121277504154758,51.49227228740939],[-3.1212775045340964,51.492272287616814],[-3.121208634202072,51.49225314506824],[-3.1212086341649323,51.49225314506108],[-3.121140309559887,51.49223325507205],[-3.121140309446783,51.492233255015606],[-3.1210725513850677,51.49221262364493],[-3.121072551323344,51.49221262363596],[-3.121005380445919,51.4921912571697],[-3.121005380351999,51.49219125707979],[-3.120938817124445,51.49216916202709],[-3.120938817485146,51.49216916214559],[-3.120872882179661,51.49214634524281],[-3.1208728821258305,51.49214634531886],[-3.120807594830251,51.49212281351275],[-3.1208075947112763,51.49212281340092],[-3.1207429753200504,51.49209857385991],[-3.1207429753013223,51.49209857383814],[-3.120679043497546,51.49207363394804],[-3.1206790434385288,51.49207363391894],[-3.120615818698462,51.49204800127738],[-3.120615818798514,51.49204800125258],[-3.1205533203825335,51.4920216836704],[-3.1205533202888556,51.49202168377389],[-3.1204915672325337,51.49199468926672],[-3.1204915670722033,51.491994689255655],[-3.12043057819074,51.49196702604674],[-3.121287048135197,51.49124452358949],[-3.1212469109877268,51.49122564156378],[-3.1212469109737055,51.491225641439584],[-3.121207307859113,51.49120632498748],[-3.121207307785014,51.49120632498492],[-3.1211682507638447,51.49118657999031],[-3.1211682508375502,51.49118658005273],[-3.1211297518055017,51.49116641253228],[-3.1211297518847836,51.491166412472985],[-3.1210918225727364,51.49114582857053],[-3.1210918226890687,51.49114582871859],[-3.121054474645074,51.49112483470513],[-3.1210544744888247,51.491124834622354],[-3.1210177190961184,51.49110343689045],[-3.121017719049255,51.49110343687145],[-3.12098156750225,51.491081641940816],[-3.1209815676175894,51.49108164194156],[-3.1209460309279033,51.49105945645348],[-3.120946030929099,51.4910594565255],[-3.1209111199195307,51.49103688723718],[-3.1209111198891057,51.49103688720791],[-3.120876845196664,51.49101394099439],[-3.120876845237184,51.49101394100306],[-3.120843217301094,51.49099062485531],[-3.1208432171964877,51.49099062482079],[-3.1208102462611644,51.490966945840114],[-3.120810246336311,51.490966945783406],[-3.120777942445398,51.49094291118517],[-3.1207779425423574,51.49094291123119],[-3.120746315532713,51.49091852833738],[-3.1207463153554893,51.4909185283992],[-3.120715374860212,51.490893804634595],[-3.1207153748313936,51.49089380451962],[-3.120685130279793,51.490868747417515],[-3.120685130348136,51.49086874747131],[-3.120655590947763,51.49084336466592],[-3.1206555909344385,51.49084336470635],[-3.1206267656831015,51.49081766392916],[-3.1206267656219864,51.49081766389111],[-3.1205986633017173,51.490791652970834],[-3.120598663446852,51.49079165301341],[-3.1205712926149376,51.490765339876],[-3.1205712925838767,51.49076533985144],[-3.1205446615788777,51.49073873251071],[-3.1205446615556296,51.490738732502905],[-3.1205187784883432,51.4907118390643],[-3.1205187785412876,51.49071183906742],[-3.120493651294799,51.49068466772427],[-3.120493651205875,51.490684667700926],[-3.1204692874340236,51.49065722672882],[-3.120469287560553,51.4906572267718],[-3.120445694681971,51.49062952453235],[-3.120445694584439,51.49062952449325],[-3.120422879787012,51.49060156942286],[-3.1204228797766604,51.49060156945074],[-3.1204008500070888,51.490573370066166],[-3.1204008501192093,51.49057337007174],[-3.1203796120874627,51.49054493496459],[-3.120379612081309,51.49054493494725],[-3.1203591722561628,51.49051627277826],[-3.1203591721336577,51.490516272775416],[-3.1203395367397024,51.49048739227452],[-3.1203395367476534,51.490487392307905],[-3.1203207117637506,51.490458302274185],[-3.1203207118789655,51.490458302245635],[-3.120302703039805,51.49042901154176],[-3.1203027029713355,51.49042901150654],[-3.120285515761425,51.49039952905314],[-3.1202855158131957,51.490399529113894],[-3.120269155463867,51.49036986389325],[-3.120269155331822,51.49036986388124],[-3.120253626827214,51.490340024928024],[-3.1202536268013996,51.490340024888184],[-3.1202389348709856,51.49031002129311],[-3.1202389349826998,51.4903100213242],[-3.120225084098288,51.49027986222847],[-3.1202250841193138,51.49027986222083],[-3.1202120784995513,51.490249556810504],[-3.1202120784577825,51.49024955682392],[-3.120199922062161,51.49021911432992],[-3.120188618602584,51.49018854402283],[-3.1201886185233847,51.490188544032016],[-3.120178171420885,51.49015785522975],[-3.1201781715431354,51.49015785521547],[-3.120168583989079,51.49012705726523],[-3.120168584006736,51.490127057264104],[-3.120159858919527,51.490096159546326],[-3.1201598587894095,51.49009615956505],[-3.1201519988245496,51.49006517149004],[-3.1201519989375917,51.49006517147765],[-3.1201450064884613,51.49003410248753],[-3.1201450064696132,51.49003410248743],[-3.1201388836640893,51.49000296204481],[-3.1201388835748602,51.49000296203871],[-3.1201336322764344,51.48997175962865],[-3.1201336322716196,51.489971759616964],[-3.1201292540782952,51.48994050474506],[-3.120129254063847,51.48994050475058],[-3.1201257503070075,51.48990920693735],[-3.1201257504551982,51.489909206947836],[-3.120123122200176,51.48987787572875],[-3.120123122091192,51.48987787572166],[-3.1201213701372676,51.48984652063777],[-3.1201204950118195,51.48981515124264],[-3.1215602010223513,51.48980733421323],[-3.1215606390767223,51.489823018906115],[-3.1215615155486947,51.48983869643675],[-3.121561515603186,51.48983869644028],[-3.121562830222075,51.489854362033846],[-3.1215628301479796,51.4898543620286],[-3.1215645825156755,51.48987001091393],[-3.1215667721061418,51.48988563832057],[-3.1215693982410566,51.489901239499645],[-3.121569398285668,51.489901239502686],[-3.121572460167846,51.48991680968699],[-3.121575956876788,51.48993234413994],[-3.121575956820267,51.48993234414613],[-3.121579887272608,51.48994783813637],[-3.121579887337667,51.489947838126994],[-3.12158425034557,51.489963286933566],[-3.1215890445718872,51.48997868585193],[-3.121589044510764,51.489978685859064],[-3.1215942685134,51.489994030197984],[-3.1215942685529976,51.48999403019339],[-3.1215999207319176,51.49000931527822],[-3.1216059993609577,51.49002453645504],[-3.121605999381838,51.49002453644833],[-3.121612502619629,51.490039689077],[-3.12161942847035,51.49005476854761],[-3.1216194284144945,51.49005476853206],[-3.121626774789338,51.49006977024404],[-3.121626774802244,51.490069770263965],[-3.1216345394542895,51.490084689650644],[-3.1216345395203158,51.49008468965665],[-3.121642720084347,51.49009952217276],[-3.1216427200584596,51.490099522142394],[-3.121651314041934,51.49011426327075],[-3.1216513140761615,51.490114263288355],[-3.121660318862948,51.490128908537905],[-3.121660318805344,51.49012890855219],[-3.121669731652724,51.49014345346277],[-3.1216697316487556,51.490143453446066],[-3.1216795496889755,51.490157893586456],[-3.1216795497502328,51.490157893587885],[-3.1216897699934347,51.49017222455867],[-3.1217003893300093,51.490186442003704],[-3.1217003892739474,51.49018644200092],[-3.1217114044629533,51.49020054157264],[-3.121711404468126,51.49020054155868],[-3.121722812157284,51.4902145189701],[-3.1217228122060496,51.49021451898964],[-3.1217346089216598,51.49022836998253],[-3.121734608858393,51.49022836996103],[-3.121746791006176,51.49024209031733],[-3.1217467910506382,51.490242090329],[-3.1217593549209544,51.49025567586807],[-3.1217593548944853,51.4902556758665],[-3.121772296660111,51.490269122450705],[-3.121785612390865,51.49028242598745],[-3.1217856124063887,51.49028242599973],[-3.1217992980233378,51.49029558242865],[-3.1217992979507754,51.49029558240736],[-3.1218133492960285,51.49030858772562],[-3.121813349326579,51.490308587744664],[-3.1218277621212653,51.490321437989465],[-3.121827762127934,51.49032143796925],[-3.121842531980825,51.49033412922642],[-3.121842531946653,51.49033412919952],[-3.1218576543586725,51.49034665760349],[-3.1218576543730814,51.490346657660965],[-3.121873124740277,51.4903590193948],[-3.1218731248288862,51.4903590193639],[-3.1218889384364656,51.49037121066113],[-3.1218889383879858,51.49037121063812],[-3.121905090419266,51.49038322778654],[-3.121905090381692,51.490383227814895],[-3.121921575918143,51.49039506715371],[-3.121921575970449,51.49039506717097],[-3.121938389990167,51.490406725092676],[-3.121938389969902,51.49040672508834],[-3.1219555273506154,51.49041819804245],[-3.121955527365826,51.490418198057085],[-3.121972982887882,51.49042948254832],[-3.121972982887285,51.49042948251232],[-3.1219907512321527,51.49044057510334],[-3.1219907511744776,51.490440575102966],[-3.1220088269307547,51.490451472415366],[-3.1220088269541906,51.49045147242486],[-3.122027204616099,51.49046217113816],[-3.1220272046942195,51.49046217117956],[-3.1220458786645944,51.4904726680341],[-3.1220458786064302,51.49047266796008],[-3.122064843193707,51.490482959759774],[-3.122064843154066,51.49048295978943],[-3.122084092584309,51.49049304339895],[-3.1220840925474582,51.490493043367756],[-3.1221036209553326,51.490502915715375],[-3.1221036209923816,51.490502915716654],[-3.122123422430163,51.49051257379423],[-3.1221234224371757,51.490512573856336],[-3.122143490874729,51.490522014722096],[-3.121287048049032,51.49124452354496],[-3.12132770699801,51.49126296539316],[-3.1213277071048986,51.49126296540053],[-3.1213688754711626,51.491280961451054],[-3.121368875533624,51.49128096138206],[-3.12141054077412,51.491298506153086],[-3.1214105407074277,51.49129850616962],[-3.121452690132227,51.49131559431775],[-3.121452690171563,51.49131559433714],[-3.121495310940912,51.491332220655465],[-3.1214953109534016,51.49133222066998],[-3.121538390083642,51.491348380093754],[-3.1215383901629568,51.491348380168304],[-3.1215819145325985,51.49136406777399],[-3.1215819145684844,51.491364067723296],[-3.1216258709151474,51.49137927873227],[-3.121625870674689,51.49137927865327],[-3.121670245607132,51.491394008434675],[-3.121670245669754,51.491394008494616],[-3.1217150256738493,51.49140825255785],[-3.121715025715002,51.49140825256383],[-3.121760197147747,51.49142200656977],[-3.1217601972231437,51.491422006607394],[-3.1218057463246343,51.491435266365635],[-3.121805746349399,51.4914352663704],[-3.1218516592428283,51.49144802784201],[-3.1218516589899434,51.49144802770374],[-3.1218979216846394,51.49146028700407],[-3.1213468992265154,51.49229067669012]],[[-3.1270715194339,51.490699520484654],[-3.1270457397479006,51.4907437444598],[-3.1270457399048537,51.49074374418977],[-3.1270187277500283,51.49078768055551],[-3.1270187274102397,51.490787680919794],[-3.1269904909940287,51.49083131628641],[-3.1269904912678768,51.490831316077],[-3.1269610392044913,51.490874637158534],[-3.126961039277041,51.49087463692034],[-3.126930380535774,51.49091763051773],[-3.126930380269136,51.49091763086375],[-3.1268985241699476,51.490960283876106],[-3.1268985243392198,51.49096028362668],[-3.1268654805998297,51.49100258306524],[-3.126865480329137,51.49100258334626],[-3.1268312589976555,51.491044516320414],[-3.126831259112155,51.491044516139354],[-3.1267958706232903,51.49108606987908],[-3.1267958710682766,51.491086069652496],[-3.1267593262006255,51.49112723150273],[-3.1267593258098074,51.49112723185777],[-3.1267216356768697,51.49116798927003],[-3.1267216357562595,51.49116798894393],[-3.1266828118558605,51.49120832950714],[-3.126682811563559,51.49120832984166],[-3.126642865701497,51.49124824126254],[-3.1266428659145866,51.491248241140056],[-3.1266018102697677,51.491287711265535],[-3.1266018099770903,51.491287711525906],[-3.126559657043111,51.491326728326314],[-3.1265596574708097,51.491326728064756],[-3.126516420100905,51.491365279661885],[-3.1265164203361158,51.49136527943914],[-3.1264721116982446,51.4914033540866],[-3.127629567019617,51.49193794987259],[-3.1275690789944,51.49198806524978],[-3.127569079282983,51.491988064838715],[-3.1275071992551244,51.492037513555665],[-3.1275071985604477,51.49203751407679],[-3.127443945351447,51.492086281058064],[-3.1274439458975967,51.49208628075359],[-3.127379338796598,51.49213435115558],[-3.1273793392901337,51.49213435083857],[-3.1273133979760166,51.49218171001588],[-3.1273133974205543,51.492181710297345],[-3.1272461419619515,51.49222834381029],[-3.127246142383247,51.492228343576784],[-3.1271775932804973,51.49227423722874],[-3.1271775925331267,51.49227423771584],[-3.1271077706463286,51.49231937751215],[-3.1271077709949653,51.4923193771557],[-3.127036697615953,51.492363749356045],[-3.1260274573846396,51.491722700025946],[-3.126080762419065,51.491689421334755],[-3.126080762157584,51.49168942160209],[-3.1261331286245833,51.49165556721364],[-3.126133129185106,51.491655566848316],[-3.1261845411157427,51.49162114706729],[-3.1261845407997706,51.49162114724241],[-3.126234982548844,51.49158617256426],[-3.1262349829654354,51.49158617235315],[-3.126284439157503,51.49155065342476],[-3.126284438787349,51.49155065366252],[-3.126332894370691,51.491514601313106],[-3.12633289396108,51.49151460154146],[-3.1263803341762086,51.49147802675453],[-3.1263803346972145,51.49147802636369],[-3.126426745076906,51.49144094027139],[-3.1264267448604612,51.491440940579686],[-3.1264721112881437,51.49140335448805],[-3.1253146829048255,51.490868747365106],[-3.125344222302195,51.49084336455785],[-3.125344222145396,51.49084336470635],[-3.125373047396733,51.49081766392916],[-3.1253730471116006,51.49081766410353],[-3.1254011494378604,51.490791653187],[-3.125401149632982,51.49079165301341],[-3.125428520464897,51.490765339876],[-3.1254285203228345,51.49076533995765],[-3.125455151330829,51.49073873261878],[-3.1254551515257023,51.49073873239576],[-3.1254810345899844,51.490711838957154],[-3.1254810345370605,51.49071183917456],[-3.125506161786538,51.49068466783141],[-3.1255061620470865,51.49068466759472],[-3.1255305258159414,51.49065722662074],[-3.1255305255192813,51.4906572267718],[-3.1255541183978632,51.49062952453235],[-3.125554118321533,51.49062952465304],[-3.125576933123452,51.490601569584506],[-3.125576933303917,51.49060156939717],[-3.125598963071997,51.49057337001259],[-3.1255989629591348,51.49057337017888],[-3.1256202009938754,51.490544935071725],[-3.125620201171645,51.49054493484104],[-3.1256406409937982,51.49051627267019],[-3.12564064094543,51.49051627282899],[-3.125660276340881,51.490487392328085],[-3.125660276158316,51.49048739246769],[-3.125679101146711,51.49045830243585],[-3.125679101373237,51.49045830219299],[-3.125697110210906,51.490429011487244],[-3.1256971101062656,51.49042901166726],[-3.1257142973206644,51.490399529213846],[-3.1270715194339,51.490699520484654]]]]} \ No newline at end of file diff --git a/benchmarks/run.sh b/benchmarks/run.sh index 3102024..0f0412e 100755 --- a/benchmarks/run.sh +++ b/benchmarks/run.sh @@ -3,7 +3,7 @@ set -e export RUST_BACKTRACE=1 -export RUST_LOG=trace +export RUST_LOG=debug PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" diff --git a/crates/total-viewsheds/src/main.rs b/crates/total-viewsheds/src/main.rs index e39f937..dfedfa7 100644 --- a/crates/total-viewsheds/src/main.rs +++ b/crates/total-viewsheds/src/main.rs @@ -77,9 +77,11 @@ mod output { /// Load, parse and reconstruct euclidean polygon viewsheds from their raw polar segments. pub mod viewsheds { - pub mod join; + pub mod growable_polygon; + pub mod joiner_common; + pub mod joiner_final; + pub mod segment_polygon; pub mod viewshed; - pub mod visible_polygon; } } diff --git a/crates/total-viewsheds/src/output/ascii.rs b/crates/total-viewsheds/src/output/ascii.rs index dc2d92d..fb6f72e 100644 --- a/crates/total-viewsheds/src/output/ascii.rs +++ b/crates/total-viewsheds/src/output/ascii.rs @@ -3,6 +3,8 @@ #![cfg(test)] #![expect(clippy::indexing_slicing, reason = "This code is mostly for tests")] +use geo::CoordsIter as _; + use crate::output::viewsheds::viewshed::Viewshed; pub fn make_viewshed( @@ -28,31 +30,90 @@ pub fn make_viewshed( pov_coord, }; - let scale = 2; + for polygons in viewshed.iter_mut() { + polygons.exterior_mut(|line| { + for coordinate in line.coords_mut() { + *coordinate = viewsheder + .convert_viewshed_coord_to_dem_coord( + crate::output::viewsheds::viewshed::Coordinate(*coordinate), + ) + .unwrap(); + } + }); + + polygons.interiors_mut(|interior| { + for interiror_line in interior { + for coordinate in interiror_line.coords_mut() { + *coordinate = viewsheder + .convert_viewshed_coord_to_dem_coord( + crate::output::viewsheds::viewshed::Coordinate(*coordinate), + ) + .unwrap(); + } + } + }); + } + + rasterise_multi_polygon(&viewshed, dem.width) +} + +pub fn rasterise_multi_polygon(multi_polygon: &geo::MultiPolygon, width: u32) -> Vec { + let scale = std::env::var("ASCII_TEST_SIZE") + .unwrap_or_else(|_| "2".to_owned()) + .parse::() + .expect("ASCII_TEST_SIZE env not a valid integer"); let scale_f64 = f64::from(scale); - let raster_width = usize::try_from(dem.width * scale).unwrap(); + let raster_width = usize::try_from(width * scale).unwrap(); let mut raster: Vec> = Vec::new(); - for _ in 0..dem.width * scale { + for _ in 0..width * scale { let raster_line = vec![false; raster_width]; raster.push(raster_line); } - for point in viewshed.iter_mut() { - point.exterior_mut(|line| { - let mut maybe_from = None; - for coordinate in line.coords_mut() { - let projected = viewsheder - .convert_viewshed_coord_to_dem_coord( - crate::output::viewsheds::viewshed::Coordinate(*coordinate), - ) - .unwrap(); - if maybe_from.is_none() { - maybe_from = Some(projected); + for polygon in multi_polygon.iter() { + let mut maybe_exterior_start = None; + for coordinate in polygon.exterior_coords_iter() { + if maybe_exterior_start.is_none() { + maybe_exterior_start = Some(coordinate); + continue; + } + + let from = maybe_exterior_start.unwrap(); + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "Gotta rasterise" + )] + let rasteriser = crate::output::bresenham::Bresenham::new( + super::bresenham::RasterCoord { + x: (from.x * scale_f64).round() as i32, + y: (from.y * scale_f64).round() as i32, + }, + super::bresenham::RasterCoord { + x: (coordinate.x * scale_f64).round() as i32, + y: (coordinate.y * scale_f64).round() as i32, + }, + ); + for coord in rasteriser { + if coord.x >= 0i32 && coord.y >= 0i32 { + let x = usize::try_from(coord.x).unwrap(); + let y = usize::try_from(coord.y).unwrap(); + raster[y][x] = true; + } + } + maybe_exterior_start = Some(coordinate); + } + + for interiors in polygon.interiors() { + let mut maybe_interior_start = None; + for coordinate in interiors { + if maybe_interior_start.is_none() { + maybe_interior_start = Some(*coordinate); continue; } - let from = maybe_from.unwrap(); + let from = maybe_interior_start.unwrap(); #[expect( clippy::as_conversions, clippy::cast_possible_truncation, @@ -64,8 +125,8 @@ pub fn make_viewshed( y: (from.y * scale_f64).round() as i32, }, super::bresenham::RasterCoord { - x: (projected.x * scale_f64).round() as i32, - y: (projected.y * scale_f64).round() as i32, + x: (coordinate.x * scale_f64).round() as i32, + y: (coordinate.y * scale_f64).round() as i32, }, ); for coord in rasteriser { @@ -75,9 +136,9 @@ pub fn make_viewshed( raster[y][x] = true; } } - maybe_from = Some(projected); + maybe_interior_start = Some(*coordinate); } - }); + } } let mut ascii = Vec::new(); @@ -103,8 +164,35 @@ pub fn make_viewshed( ascii } +pub fn rasterise(mut multi_polygon: geo::MultiPolygon) -> Vec { + let width = 12u32; + let centre = f64::from(width.div_euclid(2)); + for polygons in multi_polygon.iter_mut() { + polygons.exterior_mut(|line| { + for coordinate in line.coords_mut() { + *coordinate = geo::Coord { + x: coordinate.x + centre, + y: coordinate.y + centre, + }; + } + }); + + polygons.interiors_mut(|interior| { + for interiror_line in interior { + for coordinate in interiror_line.coords_mut() { + *coordinate = geo::Coord { + x: coordinate.x + centre, + y: coordinate.y + centre, + }; + } + } + }); + } + crate::output::ascii::rasterise_multi_polygon(&multi_polygon, width) +} + #[expect(clippy::print_stderr, reason = "This is for tests")] -pub fn assert_viewshed(actual: &[String], expected: &[&str]) { +pub fn assert_rasterised(actual: &[String], expected: &[&str]) { if actual != expected { eprintln!("Actual:"); eprint!("{}", actual.join("\n")); diff --git a/crates/total-viewsheds/src/output/bresenham.rs b/crates/total-viewsheds/src/output/bresenham.rs index a31fed7..f4e49dc 100644 --- a/crates/total-viewsheds/src/output/bresenham.rs +++ b/crates/total-viewsheds/src/output/bresenham.rs @@ -1,6 +1,6 @@ #![cfg(test)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] /// A raster coodinate. pub struct RasterCoord { /// The x coordinate. diff --git a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs new file mode 100644 index 0000000..995c314 --- /dev/null +++ b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs @@ -0,0 +1,431 @@ +//! Growable polygons are polygons constructed one angle at a time from polar segments. + +use color_eyre::eyre::{ContextCompat as _, Result, bail}; + +/// An opening is where one polygon can join a segment, or even another polygon. +/// The `u32` values in the variants are for storing the polar distance from the centre. This saves +/// having to do trigonometry to figure out if two openings are touching. +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Opening { + /// No opening. We could also just use `Option::None`, but unwrapping it isn't so ergonomic. + Null, + /// This i for polygons that start at 0 degrees. It's possible that another polygon (or even + /// itself), could connect to it at 360 degrees. + GenesisStart(u32), + /// The closing of the genesis opening at 0 degrees. + GenesisEnd(u32), + /// An active opening's start. + Start(u32), + /// An active opening's end. + End(u32), + /// When a segment or polygon is successfully attached to an existing polygon, but we still want + /// to carry on looking for other attachments in the given angle, we don't want to mistakenly + /// attach new segments/polygons to something that's just been attached. Once the whole angle is + /// completed, then these get downgraded to the common `Start/End` opendings. + NewStart(u32), + /// A `New` openings end. + NewEnd(u32), +} + +/// A vertex is a single point in a polygon. +#[derive(Debug, Clone)] +pub struct Vertex { + /// The coordinate of the vertex in euclidian space. + pub coordinate: geo::Coord, + /// The kind of opening that this vertex represents. + pub opening: Opening, +} + +impl Vertex { + /// Is the vertex at the centre of the viewshed? The centre is a special place, it is a + /// zero-length opening that isn't nulled if no segments/polygons touch it for a given angle. + pub fn is_centre(&self) -> bool { + self.coordinate.x.abs() == 0.0 && self.coordinate.y.abs() == 0.0 + } +} + +/// A polygon that grows, but only from its anti-clockwise facing side. +#[derive(Debug, Clone)] +pub struct GrowablePolygon { + /// The main exterior vertices. + pub vertices: Vec, + /// Interiore holes within the polygon. + pub holes: Vec>, + /// The distance of the opening furthest from the centre of the viewshed. Used for ordering + /// active polygons. Polygons must be joined to new segments (and other polygons) in increasing + /// order to prevent overlaps. + pub furthest_opening: u32, + /// Has the polygon been touched by anything in the current angle? If not, then it is marked as + /// an isolated polygon that doesn't need to be checked for touches on subsequent angles. + pub is_touched: bool, + /// Was the polygon created at 0 degrees? This is needed because the polygon may need to be + /// completed by joining another polygon that wraps into it at 360 degrees. + pub is_created_at_angle_0: bool, +} + +impl GrowablePolygon { + /// Create a new growable polygon. It always begins with a single polar segment converted to + /// its euclidean coordinates. + pub fn new(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { + let mut vertices = Vec::new(); + for (segment_index, segment_vertex) in segment_vertices.vertices.iter().enumerate() { + let opening = match segment_index { + 1 => { + if angle == 0.0 { + Opening::GenesisStart(segment_vertices.distances.start) + } else { + Opening::Null + } + } + 2 => { + if angle == 0.0 { + Opening::GenesisEnd(segment_vertices.distances.end) + } else { + Opening::Null + } + } + 3 => Opening::NewEnd(segment_vertices.distances.end), + 4 => Opening::NewStart(segment_vertices.distances.start), + _ => Opening::Null, + }; + vertices.push(Vertex { + coordinate: *segment_vertex, + opening: opening.clone(), + }); + } + + Self { + vertices, + holes: Vec::new(), + furthest_opening: segment_vertices.distances.start, + is_touched: false, + is_created_at_angle_0: false, + } + } + + /// Create a new polygon ready to be inserted into an existing polygon. + /// + /// Note that we wind our polygons anti-clockwise. The normal new polygon is on the left with + /// indices (abcde). And the inserted polygon is on the right with vertices (cdeb). Note how the + /// inserted polygon can't wind in the same way. It's been opened up into a straight line. The + /// break happens between "c" and "b" such that they then become the ends of the line. + /// + /// z┌──┐y z┌──┐y + /// d┌──┐c │ │ d┌─┘c │ + /// │ │ + │ │ = │ │ + /// a/e└──┘b │ │ e└─┐b │ + /// w└──┘x w└──┘x + /// + /// (abcde) + (wxyz) = (wxyzcdeb) + fn new_for_insertion(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { + let mut polygon = Self::new(segment_vertices, angle); + + #[expect( + clippy::indexing_slicing, + reason = "An new polygon MUST have 5 vertices by definition" + )] + let second = { polygon.vertices[1].clone() }; + + // Removing the first vertex doesn't destroy any data, because the first and last vertices + // are duplicates. + polygon.vertices.remove(0); + // Removing the second vertex also doesn't destroy any data because we copied it earlier. + polygon.vertices.remove(0); + + polygon.vertices.push(second); + polygon + } + + /// Once all the segments for an angle have been checked we convert the openings in the new + /// polygon as follows: + /// * `Opening::NewStart/NewEnd` become `Opening::Start/End`. + /// * `Opening::Start/End` become `Opening::Null`. + pub fn downgrade_openings(&mut self) -> Result<()> { + tracing::trace!("Downgrading openings"); + let mut iterator = self.vertices.iter_mut().rev(); + while let Some(vertex) = iterator.next() { + match vertex.opening { + super::growable_polygon::Opening::Null + | super::growable_polygon::Opening::GenesisStart(_) + | super::growable_polygon::Opening::GenesisEnd(_) => {} + super::growable_polygon::Opening::End(_) => { + bail!("Dangling `Opening`"); + } + super::growable_polygon::Opening::Start(_) => { + let Some(next) = iterator.next() else { + bail!("`Opening::Start` without adjacent end"); + }; + + vertex.opening = Opening::Null; + next.opening = Opening::Null; + } + super::growable_polygon::Opening::NewStart(at) => { + vertex.opening = Opening::Start(at); + } + super::growable_polygon::Opening::NewEnd(at) => { + vertex.opening = Opening::End(at); + } + } + } + + Ok(()) + } + + /// Insert new vertices into the polygon. Can be either a segment or an entire other growable + /// polygon that has _just_ been joined to the segment. Note that even though we could be + /// joinging a whole new polygon, it will still only ever be limited to the known case of the + /// freshly joined _segment_ touching this polygon. Therefore, we can assume things like, no + /// holes will ever be created on the _incoming_ polygon because only the segment section of it + /// is touching this currently instantiated polygon. + fn join( + &mut self, + base_vertices_range: std::ops::Range, + joining_vertices: Vec, + ) -> Result<()> { + let old_start = self.extract_old_start(base_vertices_range.end)?; + + let removed_vertices: Vec = self + .vertices + .splice(base_vertices_range.clone(), joining_vertices) + .collect(); + + self.vertices + .get_mut(base_vertices_range.start) + .context("Couldn't get new opening start index")? + .opening = old_start; + + self.dedup_vertices_but_keep_openings(); + self.create_holes(&removed_vertices); + + Ok(()) + } + + /// Insert a segment into the polygon. + pub fn join_segment( + &mut self, + segment_vertices: &super::segment_polygon::Vertices, + vertices_range: std::ops::Range, + angle: f32, + ) -> Result<()> { + tracing::trace!( + "Splicing new segment ({:?}) at: {vertices_range:?}", + segment_vertices.distances + ); + + let segment = Self::new_for_insertion(segment_vertices, angle); + self.join(vertices_range, segment.vertices)?; + + if segment.furthest_opening < self.furthest_opening { + self.furthest_opening = segment.furthest_opening; + } + + Ok(()) + } + + /// Insert another polygon into the `self` polygon, where `self` isn't the final polygon. + /// + /// Consider this situation. "A" and "B" are _existing_ active polygons. "s" is an incoming + /// segment. "s" joins to polygon "A" creating a new polygon "A+s". This new polygon then has to + /// also connect to polygon "B". + /// + /// `NewEnd`┌─┐x┌───────┐ + /// │ │ │ B │ Base polygon + /// │ │ └───────┘ + /// │s│ + /// │ │────────┐ + /// │ │ A │ Joining polygon + /// `NewStart`└─┘────────┘ + /// + /// We know that a segment always has `NewEnd` and `NewStart` openings. Therefore we can + /// calculate the index "x" at which the new polygon should be inserted by finding `NewEnd` and + /// substracting 1. + pub fn join_non_starting_polygon( + &mut self, + base_vertices_range: std::ops::Range, + joining_polygon: &mut Self, + ) -> Result<()> { + let mut maybe_joining_new_end_index = None; + for (index, vertex) in joining_polygon.vertices.iter_mut().enumerate().rev() { + // Find where in the joining polygon we are opening up to be joined by the base polygon. + if matches!(vertex.opening, Opening::NewEnd(_)) { + maybe_joining_new_end_index = Some(index); + } + + // Nullify all openings so we don't try to join them in the future. + // TODO: why can't this be done via the `downgrade_openings()` method? + let is_start = matches!(vertex.opening, Opening::Start(_)); + let is_end = matches!(vertex.opening, Opening::End(_)); + if is_start || is_end { + vertex.opening = Opening::Null; + } + } + let Some(joining_new_end_index) = maybe_joining_new_end_index else { + bail!("Couldn't find `Opening::NewEnd` for joining polygon"); + }; + + // We can assume that the vertex at which we join the incoming polygon is always 1 before + // the base polygon's `NewEnd` opening. + let joining_vertices_entry = joining_new_end_index - 1; + + // Rotating achieves the effect of unlooping the polygon at the point at which the polygon + // is joined. + joining_polygon.vertices.rotate_left(joining_vertices_entry); + + tracing::trace!( + "Splicing existing polygon at {base_vertices_range:?}, \ + joining polygon: {joining_polygon:#?}, rotated at {joining_vertices_entry}", + ); + + self.join(base_vertices_range, joining_polygon.vertices.clone())?; + + self.holes.extend(joining_polygon.holes.clone()); + + if joining_polygon.furthest_opening < self.furthest_opening { + self.furthest_opening = joining_polygon.furthest_opening; + } + + if joining_polygon.is_created_at_angle_0 { + self.is_created_at_angle_0 = true; + } + + Ok(()) + } + + /// Insert a starting polygon (from angle 0) into a final polygon (from angle ~360). + pub fn join_starting_polygon( + &mut self, + base_vertices_range: std::ops::Range, + joining_vertices_range: std::ops::Range, + joining_polygon: &mut Self, + ) -> Result<()> { + tracing::trace!( + "Splicing final polygon at {base_vertices_range:?}, \ + with: {:#?} at {joining_vertices_range:?}", + joining_polygon.vertices + ); + + // Nullify the Genesis markers + joining_polygon + .vertices + .get_mut(joining_vertices_range.start) + .context("Bad joining vertex index")? + .opening = Opening::Null; + joining_polygon + .vertices + .get_mut(joining_vertices_range.end) + .context("Bad joining vertex index")? + .opening = Opening::Null; + + // Rotating achieves the effect of unlooping the polygon at the point at which the polygon + // is joined. + joining_polygon + .vertices + .rotate_left(joining_vertices_range.start); + + self.join(base_vertices_range, joining_polygon.vertices.clone())?; + + self.holes.extend(joining_polygon.holes.clone()); + + Ok(()) + } + + /// Join a polygon into itself. + pub fn join_self( + &mut self, + left_range: std::ops::Range, + right_range: std::ops::Range, + ) { + let range = if right_range.start > left_range.start { + left_range.start..right_range.start + } else { + right_range.start..left_range.start + }; + + tracing::trace!( + "Splicing polygon into itself at: {range:?} ({left_range:?}/{right_range:?})" + ); + + let removed_vertices: Vec = self.vertices.splice(range, vec![]).collect(); + self.create_holes(&removed_vertices); + } + + /// Extract the starting vertex of an opening that has just been joined to. + pub fn extract_old_start(&mut self, index: usize) -> Result { + let vertex = self + .vertices + .get_mut(index) + .context("Bad index for old opening start")?; + + let old_start = vertex.opening.clone(); + vertex.opening = Opening::Null; + + Ok(old_start) + } + + /// Create interior holes from the vertices that were removed for the join. + fn create_holes(&mut self, vertices: &[Vertex]) { + let holes = vertices.split_inclusive(|vertex| matches!(vertex.opening, Opening::End(_))); + + for hole in holes { + if hole.iter().any(|vertex| !vertex.is_centre()) { + tracing::trace!("Hole: {hole:?}"); + self.holes.push(hole.to_vec()); + } + } + } + + /// Is a segment and a polygon, or 2 polygons, touching? We decide by whether their openings are + /// overlapping. + pub const fn is_touching(left: &std::ops::Range, right: &std::ops::Range) -> bool { + left.start < right.end && right.start < left.end + } + + /// Convert the polygon to the `geo` crate's representation. Ready for exporting to `GeoJSON`. + pub fn to_geo_polygon(&self) -> geo::Polygon { + let holes: Vec = self + .holes + .iter() + .map(|hole| { + geo::LineString( + hole.iter() + .map(|vertex| vertex.coordinate) + .collect::>(), + ) + }) + .collect(); + + let vertices: Vec = self + .vertices + .iter() + .map(|vertex| vertex.coordinate) + .collect(); + geo::Polygon::new(geo::LineString(vertices), holes) + } + + /// Remove adjacent vertices that are either identical or extremely similar. This reduces the + /// byte size of the final polygon and speeds up the algorithm by about 10%. + // + // TODO: Could the algorithm be improved so that this wasn't needed? + fn dedup_vertices_but_keep_openings(&mut self) { + self.vertices.dedup_by(|left, right| { + if !matches!(left.opening, Opening::Null) || !matches!(right.opening, Opening::Null) { + return false; + } + Self::are_coordinates_within_tolerance(&left.coordinate, &right.coordinate) + }); + } + + /// Dedupe vertices, but allow destroying opening metadata. This is useful right at the very end + /// of the reconstruction. + pub fn dedup_vertices_ignore_openings(&mut self) { + self.vertices.dedup_by(|left, right| { + Self::are_coordinates_within_tolerance(&left.coordinate, &right.coordinate) + }); + } + + /// Are the two coordinates identical or as good as identical. + fn are_coordinates_within_tolerance(left: &geo::Coord, right: &geo::Coord) -> bool { + let tolerance = 1e-6f64; + (left.x - right.x).abs() < tolerance && (left.y - right.y).abs() < tolerance + } +} diff --git a/crates/total-viewsheds/src/output/viewsheds/join.rs b/crates/total-viewsheds/src/output/viewsheds/join.rs deleted file mode 100644 index be47313..0000000 --- a/crates/total-viewsheds/src/output/viewsheds/join.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Join the visible segments from each computed angle into the viewshed. - -impl super::visible_polygon::VisiblePolygon { - /// Convert polar segments to euclidean polygons. - pub fn parse_polar_segments( - data: &[Vec], - scale: f32, - ) -> geo::MultiPolygon { - let angle_count = data.len(); - let mut polygons = Vec::new(); - for (anglish, segments) in data.iter().enumerate() { - #[expect( - clippy::as_conversions, - clippy::cast_precision_loss, - reason = "The angle count should never strain the f32 mantissa" - )] - let (anglish_f32, angle_count_f32) = { (anglish as f32, angle_count as f32) }; - let polygoner = Self { - scale, - current_angle: (anglish_f32 / angle_count_f32) * 360.0, - }; - for segment in segments { - let opening = u32::from(segment.start()); - let closing = u32::from(segment.start() + segment.distance()); - let polygon = polygoner.make_visible_polygon(opening, closing); - polygons.push(polygon); - } - } - - geo::unary_union(polygons.iter()) - } -} diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner_common.rs b/crates/total-viewsheds/src/output/viewsheds/joiner_common.rs new file mode 100644 index 0000000..2fa1de3 --- /dev/null +++ b/crates/total-viewsheds/src/output/viewsheds/joiner_common.rs @@ -0,0 +1,763 @@ +//! Join the visible segments from each computed angle into the viewshed. These "common" joinings +//! are the ones the occur for every angle other than the final one. Therefore these should simpler +//! and faster. + +use color_eyre::eyre::{Result, bail}; + +/// Keeps track of active and completed polygons within a viewshed. +#[derive(Default)] +pub struct Joiner { + /// Polygons that don't intersect with the current angle. They don't need to be checked against + /// new segments. + pub completed: Vec, + /// Polygons that intersect with the current angle. They must be checked to see if any of their + /// openings touch any of the segments of the current angle. + pub active: Vec, +} + +impl Joiner { + /// Build a viewshed of euclidean polygons from polar segments. + pub fn build( + data: &[Vec], + dem_scale: f32, + ) -> Result { + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "The angle count should never strain the f32 mantissa" + )] + let angle_count = data.len() as f32; + let angle_scale = angle_count / 360.0; + + let mut joiner = Self { + completed: Vec::new(), + active: Vec::new(), + }; + + for (anglish, segments) in data.iter().enumerate() { + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "The angle count should never strain the f32 mantissa" + )] + let angle = anglish as f32 / angle_scale; + + joiner.build_angle(angle, angle_scale, segments, dem_scale)?; + } + + joiner.build_final_angle()?; + joiner.move_all_active_polygons_to_completed(); + + let mut geo_polygons = Vec::new(); + for mut raw_polygon in joiner.completed { + raw_polygon.dedup_vertices_ignore_openings(); + let polygon = raw_polygon.to_geo_polygon(); + tracing::trace!("Final viewshed, adding polygon: {polygon:?}"); + geo_polygons.push(polygon); + } + + Ok(geo::MultiPolygon::new(geo_polygons)) + } + + /// Build the viewshed for a single angle. + fn build_angle( + &mut self, + angle: f32, + angle_scale: f32, + segments: &[crate::storage::segments::Segment], + dem_scale: f32, + ) -> Result<()> { + tracing::debug!(""); + tracing::debug!("Building viewshed for angle: {angle}"); + tracing::debug!( + "Polygon counts, completed: {}, active: {}. Segments: {}", + self.completed.len(), + self.active.len(), + segments.len() + ); + + self.prepare_active_polygons(); + + let mut new_polygons = Vec::new(); + + let polygoner = super::segment_polygon::SegmentPolygon { + dem_scale, + angle, + angle_scale, + }; + + let timing = std::time::Instant::now(); + let last_segment_index = segments.len().saturating_sub(1); + for (segment_index, polar_segment) in segments.iter().enumerate() { + let is_last_segment = segment_index == last_segment_index; + let start = u32::from(polar_segment.start()); + let end = u32::from(polar_segment.start() + polar_segment.distance()); + tracing::debug!("Segment {segment_index}, distances range: {:?}", start..end); + let polygon_segment = polygoner.make(start, end); + let mut is_segment_touching_anything = false; + let mut maybe_joining_polygon_index = None; + let mut joined_polygons_to_remove = Vec::new(); + + for active_polygon_index in 0..self.active.len() { + let touchging_timing = std::time::Instant::now(); + + let is_segment_touching_this_polygon = self.join_segment( + active_polygon_index, + &polygon_segment, + maybe_joining_polygon_index, + angle, + )?; + + #[expect( + clippy::indexing_slicing, + reason = "We're getting the indexes from `.len()`" + )] + let active_polygon = &mut self.active[active_polygon_index]; + + if is_segment_touching_this_polygon { + active_polygon.is_touched = true; + is_segment_touching_anything = true; + if let Some(joining_polygon_index) = maybe_joining_polygon_index { + joined_polygons_to_remove.push(joining_polygon_index); + } else { + maybe_joining_polygon_index = Some(active_polygon_index); + } + } else { + maybe_joining_polygon_index = None; + } + + tracing::debug!( + "Segment checked against active {active_polygon_index} in {:?}", + touchging_timing.elapsed() + ); + } + + tracing::debug!("Removing joined polygons: {joined_polygons_to_remove:?}"); + for joined_polygon_index in joined_polygons_to_remove { + self.active.remove(joined_polygon_index); + } + + if is_last_segment { + for polygon in &mut self.active { + polygon.downgrade_openings()?; + } + } + + if !is_segment_touching_anything { + new_polygons.push(Self::create_new_polygon_from_untouched_segment( + &polygon_segment, + angle, + )?); + } + } + tracing::debug!("Angle {angle} done in {:?}", timing.elapsed()); + + self.move_untouched_active_polygons_to_completed(); + self.active.extend(new_polygons); + + Ok(()) + } + + /// When a segment doesn't touch anything it becomes its own independent polygon. + fn create_new_polygon_from_untouched_segment( + polygon_segment: &super::segment_polygon::Vertices, + angle: f32, + ) -> Result { + tracing::debug!( + "Segment not touching anything at angle {angle}, \ + so making it its own polygon: {polygon_segment:?}" + ); + let mut polygon = super::growable_polygon::GrowablePolygon::new(polygon_segment, angle); + if angle == 0.0 { + polygon.is_created_at_angle_0 = true; + } + polygon.downgrade_openings()?; + + Ok(polygon) + } + + /// Set all active polygons to untouched and order them based on their openings' distance from + /// the centre. + pub(crate) fn prepare_active_polygons(&mut self) { + self.active.sort_by_key(|polygon| polygon.furthest_opening); + self.active.iter_mut().for_each(|active| { + active.is_touched = false; + }); + } + + /// If a polygon wasn't touched by a segment from the current angle then it is moved to `self.completed`. + fn move_untouched_active_polygons_to_completed(&mut self) { + let completed = self + .active + .extract_if(.., |polygon| !polygon.is_touched) + .collect::>(); + self.completed.extend(completed); + } + + /// Once all angles have been checked, we can move all active polygons to "completed". + fn move_all_active_polygons_to_completed(&mut self) { + let active = self.active.drain(..); + self.completed.extend(active); + } + + /// This is the essential case for joining a new segment (B) to a single existing viewshed + /// polygon (A): + /// + /// ┌───────┐ + /// ┌───┐c │ + /// │ │───┐ │ + /// │ B │ h │ │ + /// │ │───┘ │ + /// └───┘b A │ + /// └───┐ │ + /// │ │ + /// ┌───┘ │ + /// │a │ + /// └───────┘ + /// + /// * Polygon A is an existing "active", complex [`GrowablePolygon`]. + /// * Polygon B is a new simple segment, represented by 5 vertices ([`SegmentPolygonVertices`]). + /// * Polygon A has 1 or more "openings" (represented here as "a", "b" and "c"). If it has + /// more than 1 opening there will be holes, that need to be created separately. A hole-to-be + /// is represented here by "h". + /// * Polygon B's vertices replace polygon A's vertices starting at the end of the first + /// touching opening and ending at the start of the last touching opening. + /// * The replaced vertices are used to make holes. + /// + /// This is the result of the join: + /// + /// ┌───────┐ + /// ┌───┘ │ + /// │ ┌───┐ │ + /// │b │ h │ │ + /// │ └───┘ │ + /// └───┐ │ + /// └───┐ │ + /// │ │ + /// ┌───┘ │ + /// │a │ + /// └───────┘ + /// + /// * Only 1 polygon remains, therefore polygon A has grown. + /// * A hole or holes ("h") have been created. + /// * There is one less opening (just "a" and "b"). + fn join_segment( + &mut self, + growable_polygon_index: usize, + segment: &super::segment_polygon::Vertices, + maybe_joining_polygon_index: Option, + angle: f32, + ) -> Result { + let mut maybe_touching_start = None; + let mut maybe_touching_end = None; + + let (base_polygon, maybe_joining_polygon) = + self.get_involved_polygons(growable_polygon_index, maybe_joining_polygon_index); + + tracing::trace!( + "Polygon {growable_polygon_index} BEFORE: {:#?}", + base_polygon + ); + + let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); + while let Some((index, vertex)) = iterator.next() { + match vertex.opening { + super::growable_polygon::Opening::Start(opening_start) => { + let Some((index_next, vertex_next)) = iterator.next() else { + bail!("Opening without following vertex"); + }; + + let super::growable_polygon::Opening::End(opening_end) = vertex_next.opening + else { + bail!("Opening start not followed by opening end"); + }; + + let opening_range = opening_start..opening_end; + + tracing::debug!( + "Checking touch for polygon {growable_polygon_index} at opening index: \ + {index}, distances: {opening_range:?}", + ); + if super::growable_polygon::GrowablePolygon::is_touching( + &segment.distances, + &opening_range, + ) { + tracing::debug!( + "🟢 Polygon {growable_polygon_index} touches segment opening" + ); + + if maybe_touching_start.is_none() { + maybe_touching_start = Some(index); + } + maybe_touching_end = Some(index_next + 1); + } else { + tracing::debug!( + "🟡 Polygon {growable_polygon_index} does not touch segment opening" + ); + } + } + super::growable_polygon::Opening::End(_) => { + bail!("Unexpected opening end reached"); + } + super::growable_polygon::Opening::Null + | super::growable_polygon::Opening::GenesisStart(_) + | super::growable_polygon::Opening::GenesisEnd(_) + | super::growable_polygon::Opening::NewStart(_) + | super::growable_polygon::Opening::NewEnd(_) => (), + } + } + + if let Some(touching_start) = maybe_touching_start + && let Some(touching_end) = maybe_touching_end + { + let base_vertices_range = touching_end..touching_start; + match maybe_joining_polygon { + Some(joining_polygon) => { + base_polygon.join_non_starting_polygon(base_vertices_range, joining_polygon)?; + tracing::trace!( + "Polygon {growable_polygon_index} AFTER joined polygon: {:#?}", + base_polygon + ); + } + None => { + base_polygon.join_segment(segment, base_vertices_range, angle)?; + tracing::trace!( + "Polygon {growable_polygon_index} AFTER joined segment: {:#?}", + base_polygon + ); + } + } + + return Ok(true); + } + + Ok(false) + } + + /// Get the polygons involved in a touch check. + /// + /// It's in a function purely to hide the ugliness. + fn get_involved_polygons( + &mut self, + growable_polygon_index: usize, + maybe_joining_polygon_index: Option, + ) -> ( + &mut super::growable_polygon::GrowablePolygon, + Option<&mut super::growable_polygon::GrowablePolygon>, + ) { + if let Some(joining_polygon_index) = maybe_joining_polygon_index { + #[expect( + clippy::expect_used, + reason = "An error here is a broken invariant. Better to panic." + )] + // This `disjoint` shenanigans is to get 2 mutable references at once, otherwise + // the borrow checker complains. + let [growable_polygon, joining_polygon] = self + .active + .get_disjoint_mut([growable_polygon_index, joining_polygon_index]) + .expect("Polygon indexes not found"); + (growable_polygon, Some(joining_polygon)) + } else { + #[expect( + clippy::indexing_slicing, + reason = "We're getting the indexes from `.len()`" + )] + (&mut self.active[growable_polygon_index], None) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn multiple_angles() { + crate::setup_logging().unwrap(); + let segment = vec![crate::storage::segments::Segment::new(0, 5)]; + let joined = Joiner::build(&[segment.clone(), segment.clone(), segment], 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "█████████████████▀██████", + "█████████████▀▀▄▄ ██████", + "██████████▀▀▄████ ██████", + "███████▀▄▄███████ ██████", + "███▀▀▄▄██████████ ██████", + "██▄▀▀████████████ ██████", + "█████▄▄▀█████████ ██████", + "████████▄▄▀▀█████ ██████", + "████████████▄▀▀██ ██████", + "███████████████▄▄ ██████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn multiple_segments() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 5)]; + let multiple = vec![ + crate::storage::segments::Segment::new(0, 2), + crate::storage::segments::Segment::new(3, 1), + ]; + let joined = Joiner::build( + &[ + main.clone(), + main.clone(), + main.clone(), + main.clone(), + multiple, + main, + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████ ▀▀█████████", + "██████████▀▀▄██▄▄▀▀█████", + "███▀▀█▀▀▄▄▀▀ ██████▄▄▀██", + "███ █▄█▀ ▄▀▀▄████████ ██", + "███ █████▄███████████ ██", + "███ █████████████████ ██", + "███ █████████████████ ██", + "███ █████████████████ ██", + "████▄▄▀▀█████████▀▀▄▄███", + "████████▄▄▀▀█▀▀▄▄███████", + "████████████▄███████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn multiple_polygons() { + crate::setup_logging().unwrap(); + let segment = vec![crate::storage::segments::Segment::new(0, 5)]; + let joined = Joiner::build( + &[ + segment.clone(), + vec![], + vec![], + vec![], + vec![], + segment, + vec![], + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "█████████▀▀▀▀▄ █████████", + "██████▄ ▄████▀▄█████████", + "████████ ████ ██████████", + "█████████▄▀██ ████▀▀▄ ██", + "███████████ █▀▀▄▄███ ██", + "████████████▄▄▀▀█████ ██", + "████████████████▄▄▀▀█ ██", + "████████████████████▄▄██", + "████████████████████████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn multiple_polygons_not_touching() { + crate::setup_logging().unwrap(); + let segment = vec![crate::storage::segments::Segment::new(2, 2)]; + let joined = Joiner::build( + &[ + segment.clone(), + vec![], + vec![], + vec![], + vec![], + segment, + vec![], + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▀▀▀▄ █████████", + "███████▄ ████▀▄█████████", + "█████████▄▀▀▄▄████▀▀████", + "████████████████ ▄█ ████", + "████████████████ ██ ████", + "████████████████▄▄▀ ████", + "████████████████████████", + "████████████████████████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn varying_sized_segments() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 4)]; + let variance = vec![crate::storage::segments::Segment::new(0, 2)]; + let joined = Joiner::build( + &[main.clone(), main.clone(), main.clone(), main, variance], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀ █████████████", + "████████ █▄▀██████▀█████", + "██████▀▄███▄▄▀▀█▀▄ █████", + "█████ █████████▄██ █████", + "████▄▀████████████ █████", + "██████ ███████████ █████", + "███████▄▀████████▀ █████", + "█████████ ██▀▀▄▄▄███████", + "██████████▄▄████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn untouched_openings_get_closed() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 4)]; + let pair = vec![ + crate::storage::segments::Segment::new(0, 2), + crate::storage::segments::Segment::new(3, 1), + ]; + let bottom = vec![crate::storage::segments::Segment::new(0, 2)]; + let joined = Joiner::build(&[main.clone(), pair, bottom, main.clone(), main], 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▄▄▀▀▀█████████", + "████████ ██████▄▄▀▀█████", + "██████▀▄██████████ █████", + "█████ ████████████ █████", + "████▄▄▄▄▄▀████████ █████", + "██████████ █▀▀▄▄▀█ █████", + "███████████▄▀▀▀▀▄ █████", + "██████████ ▄▀▀▄▄▄███████", + "██████████▄▄████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn centre_is_touched_after_more_than_one_angle() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 4)]; + let top = vec![crate::storage::segments::Segment::new(2, 2)]; + let joined = Joiner::build( + &[ + main.clone(), + top.clone(), + top.clone(), + top.clone(), + top, + main, + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "██████████▀▀▄▀▀█████████", + "██████▀▀▄▄█████▄▄▀▀█████", + "█████ ████▀▀ ██████ ████", + "█████ ███ ██ ██████ ████", + "█████ ███ ██▄▀▀████ ████", + "█████ ███▄▀▀█▀▀▄███ ████", + "█████▄▀▀████▄████▀▀▄████", + "████████▄▄▀▀█▀▀▄▄███████", + "████████████▄███████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn a_hole() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 4)]; + let lid = vec![crate::storage::segments::Segment::new(2, 1)]; + let joined = + Joiner::build(&[main.clone(), main.clone(), lid, main.clone(), main], 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▄▄▀▀▀█████████", + "████████ ██████▄▄▀▀█████", + "██████▀▄██████████ █████", + "█████ ████████████ █████", + "████▄▄▄▀▄ ▄▄ █████ █████", + "███████▄▀▄▀ ██████ █████", + "█████████ █▄█████▀ █████", + "██████████ █▀▀▄▄▄███████", + "██████████▄▄████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn multiple_holes() { + crate::setup_logging().unwrap(); + let main = vec![crate::storage::segments::Segment::new(0, 6)]; + let struts = vec![ + crate::storage::segments::Segment::new(0, 1), + crate::storage::segments::Segment::new(2, 1), + crate::storage::segments::Segment::new(4, 1), + ]; + let joined = Joiner::build( + &[main.clone(), main.clone(), main.clone(), struts, main], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████▀▀██████████████", + "████████▀▄▄▄▄▄▀▀▀███████", + "███████▀▄▀ ██████▄▄▄▄▀▀█", + "█████▀▄█▀ ▄███████████ █", + "████▀▄▀▄▀▄▀ ██████████ █", + "███ █▀▄ █▀ ▄██████████ █", + "▄ ▄█▄▄▄█▄▄▄███████████ █", + "█▄▀███████████████████ █", + "███▄▀█████████████████ █", + "████▄▀███████████████▀ █", + "██████▄▀██████▀▀▀▄▄▄▄███", + "███████▄▀▀▄▄▄▄██████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn segment_joins_to_multiple_polygons() { + crate::setup_logging().unwrap(); + let polygons = vec![ + crate::storage::segments::Segment::new(2, 1), + crate::storage::segments::Segment::new(4, 1), + ]; + let long = vec![crate::storage::segments::Segment::new(0, 5)]; + let joined = Joiner::build( + &[ + polygons.clone(), + polygons.clone(), + polygons.clone(), + long, + polygons, + vec![], + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "██████████▀▀ ███████████", + "██████▀▀▄▄▀▀▄███████████", + "███▀▄▄▀▀▄▄▀▀ ████████▀██", + "███ █▄▄▀▄▄▀▀▄████▀█ ▄ ██", + "███ █████▄▀▀███ ▄ █ █ ██", + "███ ██████▀▀▄██ █ █ █ ██", + "███ ██▀▀█▄▀▀█▀▀▄█ █ █ ██", + "███ █▄▀▀▄▄▀▀▄▀▀▄▄▀▀▄█ ██", + "████▄▄▀▀▄▄▀▀▄▀▀▄▄▀▀▄▄███", + "████████▄▄▀▀▄▀▀▄▄███████", + "████████████▄███████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn join_in_distance_order() { + crate::setup_logging().unwrap(); + + // Even though this polygon is made first and so naturally appears first in the `self.active` + // list of polygons, it is in fact _nearer_ the centre and so must be joined before newer but + // higher polygons. + let higher_but_made_first = crate::storage::segments::Segment::new(2, 2); + + let lower = crate::storage::segments::Segment::new(0, 1); + let first = vec![higher_but_made_first.clone()]; + let second = vec![lower, higher_but_made_first]; + let long = vec![crate::storage::segments::Segment::new(0, 5)]; + let joined = Joiner::build(&[vec![], first, second, long, vec![]], 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████▀ ██████████████", + "███████▀▄█ █████████████", + "█████▀▄███ █████████████", + "████▀▄█████ ████████████", + "███ ███████▄▀███████████", + "██▄▄▄▀██▄ ▄▀ ███████████", + "██████ ██▄▀▄█▀▀▄▀███████", + "███████▄▀██▄▄████ ▀█████", + "█████████ ██▀▀▄▄▄███████", + "██████████▄▄████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn inherit_holes_common() { + crate::setup_logging().unwrap(); + + let joined = Joiner::build( + &[ + vec![], + vec![crate::storage::segments::Segment::new(0, 3)], + vec![ + crate::storage::segments::Segment::new(0, 1), + crate::storage::segments::Segment::new(2, 1), + ], + vec![ + crate::storage::segments::Segment::new(0, 3), + crate::storage::segments::Segment::new(4, 1), + ], + vec![crate::storage::segments::Segment::new(0, 5)], + vec![], + vec![], + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████▀▄▀████████████████", + "████▀▄█▄▀███████████████", + "███▀▄████▄▀█████████████", + "██▀▄██████▄▀████████████", + "██▄▀ ▄ █████▄▀▀█████████", + "███▄▀ █ ██▀ ▄▀█▄▄▀██████", + "████▄▀ █ ▀▀█▄▄▀▄▄███████", + "█████▄▀ ███▄▄▄██████████", + "██████▄█████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } +} diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner_final.rs b/crates/total-viewsheds/src/output/viewsheds/joiner_final.rs new file mode 100644 index 0000000..97c0ada --- /dev/null +++ b/crates/total-viewsheds/src/output/viewsheds/joiner_final.rs @@ -0,0 +1,476 @@ +//! This is for joining polygons that wrap over the 360°-0° boundary. These joins are a bit more +//! complicated, but it's okay if the code is less efficient as it only happens once per viewshed +//! reconstruction. + +use color_eyre::eyre::{ContextCompat as _, Result, bail}; +use itertools::Itertools as _; + +impl super::joiner_common::Joiner { + /// The final angle faces the challenge of joining polygons together from the first angle. + pub(crate) fn build_final_angle(&mut self) -> Result<()> { + tracing::trace!("Building viewshed for final angle"); + + let timing = std::time::Instant::now(); + self.active + .iter() + .sorted_by_key(|polygon| polygon.furthest_opening); + self.completed + .iter() + .sorted_by_key(|polygon| polygon.furthest_opening); + + let starting_polygon_indexes: Vec = self + .completed + .iter() + .filter(|item| item.is_created_at_angle_0) + .enumerate() + .map(|item| item.0) + .collect(); + + tracing::debug!(""); + let total_polygons = starting_polygon_indexes.len() + self.active.len(); + tracing::debug!("Final angle, polygons to check: {}", total_polygons); + + tracing::trace!( + "Final angle, completed polygon (started at angle 0) indices: {:?}. Active polygons: {:?}", + starting_polygon_indexes, + self.active.len() + ); + let mut touching_starting_polygons = Vec::new(); + for starting_polygon_index in starting_polygon_indexes { + for final_polygon_index in 0..self.active.len() { + if self + .check_polygons_touching(final_polygon_index, Some(starting_polygon_index))? + { + touching_starting_polygons.push(starting_polygon_index); + } + } + } + + // Reverse order so indices remain valid. + let polygons_to_remove = touching_starting_polygons.iter().rev(); + tracing::debug!( + "Removing final joined polygons: {:?}", + polygons_to_remove.clone().collect::>() + ); + for touching_starting_polygon in polygons_to_remove { + self.completed.remove(*touching_starting_polygon); + } + + let self_joining_polygon_indexes: Vec = self + .active + .iter() + .filter(|item| item.is_created_at_angle_0) + .enumerate() + .map(|item| item.0) + .collect(); + + for self_joining_polygon_index in self_joining_polygon_indexes { + self.check_polygons_touching(self_joining_polygon_index, None)?; + } + + tracing::debug!("Final angle done in {:?}", timing.elapsed()); + + Ok(()) + } + + /// Check whether a final and a starting polygon are touching. + fn check_polygons_touching( + &mut self, + final_polygon_index: usize, + maybe_starting_polygon_index: Option, + ) -> Result { + tracing::debug!( + "Checking final polygon {final_polygon_index} \ + against starting polygon {maybe_starting_polygon_index:?}" + ); + + let vertices_clone = if let Some(starting_polygon_index) = maybe_starting_polygon_index { + self.completed + .get_mut(starting_polygon_index) + .context("Bad polygon index")? + .vertices + .clone() + } else { + self.active + .get_mut(final_polygon_index) + .context("Bad polygon index")? + .vertices + .clone() + }; + + let mut iterator = vertices_clone.iter().enumerate().rev(); + while let Some((index, vertex)) = iterator.next() { + match vertex.opening { + super::growable_polygon::Opening::Null + | super::growable_polygon::Opening::NewStart(_) + | super::growable_polygon::Opening::NewEnd(_) + | super::growable_polygon::Opening::Start(_) + | super::growable_polygon::Opening::End(_) => (), + super::growable_polygon::Opening::GenesisStart(_) => { + bail!("Dangling `Opening::GenesisStart`"); + } + super::growable_polygon::Opening::GenesisEnd(start) => { + let Some(next) = iterator.next() else { + bail!("`Opening::GenesisEnd` without a following vertex"); + }; + let super::growable_polygon::Opening::GenesisStart(end) = next.1.opening else { + tracing::error!( + "Bad opening ({:?}) in polygon: {vertices_clone:#?}", + next.1.opening + ); + bail!("`Opening::GenesisEnd` not followed by `Opening::GenesisStart`"); + }; + + let next_index = next.0; + + let joining_distances_range = end..start; + let joining_vertices_range = index..next_index; + + let is_touching = self.join_final_polygon( + final_polygon_index, + joining_distances_range, + maybe_starting_polygon_index, + joining_vertices_range, + )?; + + if is_touching && maybe_starting_polygon_index.is_some() { + self.check_polygons_touching(final_polygon_index, None)?; + return Ok(true); + } + } + } + } + + Ok(false) + } + + /// Join a final polygon to either itself or a starting polygon. + fn join_final_polygon( + &mut self, + base_polygon_index: usize, + joining_opening_range: std::ops::Range, + maybe_joining_polygon_index: Option, + joining_vertices_range: std::ops::Range, + ) -> Result { + tracing::debug!( + "Checking base polygon {base_polygon_index} \ + opening indices: {joining_vertices_range:?}" + ); + let mut maybe_touching_start = None; + let mut maybe_touching_end = None; + + let (base_polygon, maybe_joining_polygon) = + if let Some(joining_polygon_index) = maybe_joining_polygon_index { + let base_polygon = self + .active + .get_mut(base_polygon_index) + .context("Bad polygon index")?; + let joining_polygon = self + .completed + .get_mut(joining_polygon_index) + .context("Bad polygon index")?; + (base_polygon, Some(joining_polygon)) + } else { + ( + self.active + .get_mut(base_polygon_index) + .context("Bad polygon index")?, + None, + ) + }; + + let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); + while let Some((index, vertex)) = iterator.next() { + match vertex.opening { + super::growable_polygon::Opening::Start(opening_start) => { + let Some((index_next, vertex_next)) = iterator.next() else { + bail!("Opening without following vertex"); + }; + + let super::growable_polygon::Opening::End(opening_end) = vertex_next.opening + else { + bail!("Opening `Start` not followed by opening `End`"); + }; + + let base_opening_range = opening_start..opening_end; + + tracing::trace!("Checking touching for vertex ({:?})...", vertex); + if super::growable_polygon::GrowablePolygon::is_touching( + &base_opening_range, + &joining_opening_range, + ) { + tracing::trace!( + "Final angle, polygon {base_polygon_index} touches: \ + {base_opening_range:?}/{joining_opening_range:?}" + ); + + if maybe_touching_start.is_none() { + maybe_touching_start = Some(index); + } + maybe_touching_end = Some(index_next + 1); + } else { + tracing::trace!( + "Final angle, polygon {base_polygon_index} does not touch: \ + {base_opening_range:?}/{joining_opening_range:?}" + ); + } + } + super::growable_polygon::Opening::End(_) => { + bail!("Unexpected opening `End` reached"); + } + super::growable_polygon::Opening::Null + | super::growable_polygon::Opening::GenesisStart(_) + | super::growable_polygon::Opening::GenesisEnd(_) + | super::growable_polygon::Opening::NewStart(_) + | super::growable_polygon::Opening::NewEnd(_) => (), + } + } + + if let Some(touching_start) = maybe_touching_start + && let Some(touching_end) = maybe_touching_end + { + let base_vertices_range = touching_end..touching_start; + + match maybe_joining_polygon { + Some(joining_polygon) => { + tracing::trace!("Final angle, polygon BEFORE: {:#?}", base_polygon); + base_polygon.join_starting_polygon( + base_vertices_range, + joining_vertices_range, + joining_polygon, + )?; + tracing::trace!( + "Final angle, polygon AFTER joined polygon: {:#?}", + base_polygon + ); + } + None => { + tracing::trace!("Final angle, self-polygon BEFORE: {:#?}", base_polygon); + base_polygon.join_self(joining_vertices_range, base_vertices_range); + tracing::trace!("Final angle, self-polygon AFTER: {:#?}", base_polygon); + } + } + + return Ok(true); + } + + Ok(false) + } +} + +#[cfg(test)] +mod test { + + #[test] + fn final_segment_joins_starting_segment_simple() { + crate::setup_logging().unwrap(); + let part = vec![crate::storage::segments::Segment::new(2, 2)]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build( + &[part.clone(), vec![], vec![], vec![], vec![], part], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "████████████ ▀▀█████████", + "████████████ ██▄▄▀▀█████", + "████████████▄▀▀████ ████", + "███████████████ ███ ████", + "███████████████ ███ ████", + "███████████████▄▀▀█ ████", + "██████████████████▄▄████", + "████████████████████████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn inherit_holes_final() { + crate::setup_logging().unwrap(); + let joined = crate::output::viewsheds::joiner_common::Joiner::build( + &[ + vec![crate::storage::segments::Segment::new(0, 3)], + vec![ + crate::storage::segments::Segment::new(0, 1), + crate::storage::segments::Segment::new(2, 1), + ], + vec![crate::storage::segments::Segment::new(0, 3)], + vec![], + vec![], + vec![crate::storage::segments::Segment::new(0, 5)], + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████ ▀▀█████████", + "████████████ ██▄▄▀▀█████", + "████████████ ██████▄ ▀██", + "████████████ ████▀▄▄████", + "████████████ ████ ██████", + "██████████▀▀▄▀▀██ ██████", + "███████▀▄▄██ ▀▀▄█ ██████", + "████████▄▄▀▀▄▀▀▄▄███████", + "████████████▄███████████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn final_segment_joins_2_starting_segments() { + crate::setup_logging().unwrap(); + let long = vec![crate::storage::segments::Segment::new(0, 4)]; + let starting = vec![ + crate::storage::segments::Segment::new(0, 1), + crate::storage::segments::Segment::new(2, 1), + ]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build( + &[starting, vec![], vec![], vec![], vec![], long], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "████████████ ▀▀█████████", + "████████████ ██▄▄▀▀█████", + "████████████ ████▀▄▄████", + "████████████ █▀ █ ██████", + "████████████▄▀ █ ██████", + "███████████████▄▀ ██████", + "████████████████████████", + "████████████████████████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn final_segment_joins_starting_segment_with_2_openings() { + crate::setup_logging().unwrap(); + let long = vec![crate::storage::segments::Segment::new(0, 5)]; + let starting = vec![ + crate::storage::segments::Segment::new(2, 1), + crate::storage::segments::Segment::new(4, 1), + ]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build( + &[starting, long.clone(), vec![], vec![], vec![], long], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████ ▀▀█████████", + "████████████ ██▄▄▀▀█████", + "████████████ ██████▄▄▀██", + "████████████ ████▀▄ █ ██", + "████████████ ▀▀ █ █ █ ██", + "████████████ ▀▀ █ █ █ ██", + "████████████ ██▄█ ▀ █ ██", + "████████████ ██████▄█ ██", + "████████████ ████▀▀▄▄███", + "████████████ ▀▀▄▄███████", + "████████████▄███████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn final_segment_joins_starting_segment_complex() { + crate::setup_logging().unwrap(); + let long = vec![crate::storage::segments::Segment::new(0, 4)]; + let short = vec![crate::storage::segments::Segment::new(2, 2)]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build( + &[ + short.clone(), + long.clone(), + vec![], + vec![], + vec![], + vec![], + long, + short, + ], + 1.0, + ) + .unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▀▀▀▀▀▀████████", + "█████████▄▀█████▄▀██████", + "██████████▄▀██ ▀██▄▀████", + "███████████▄▀ ██ ██ ████", + "████████████ ▄▀▀ ██ ████", + "█████████████ ██▄██ ████", + "██████████████ ██▀▄█████", + "███████████████ ▄███████", + "████████████████████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn a_ring() { + crate::setup_logging().unwrap(); + let part = vec![crate::storage::segments::Segment::new(2, 2)]; + let ring = vec![part; 10]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build(&ring, 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▀▄▄▄▀▀████████", + "██████▀▄▄███████▄▄▀█████", + "█████ ████▀▄▄▀▀███▄▀████", + "████ ███▀▄█████▄▀███ ███", + "████ ███ ███████ ███ ███", + "████▄▀███▄▀▀██▀▄███▀▄███", + "██████ █████▄▄████▀▄████", + "███████▄▄▀▀███▀▀▄▄██████", + "███████████▄▄▄██████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } + + #[test] + fn rings() { + crate::setup_logging().unwrap(); + let part = vec![ + crate::storage::segments::Segment::new(1, 1), + crate::storage::segments::Segment::new(3, 1), + ]; + let ring = vec![part; 10]; + let joined = crate::output::viewsheds::joiner_common::Joiner::build(&ring, 1.0).unwrap(); + let actual = crate::output::ascii::rasterise(joined); + let expected = [ + "████████████████████████", + "████████████████████████", + "█████████▀▀▄▄▄▀▀████████", + "██████▀▄ ▀▄▄▄▄▀▀ ▄▀█████", + "█████ █ ██▀▀▄▄▀██ ▄▀████", + "████ █ █▀▄▀▄▄▄▀▄▀█ █ ███", + "████ █ █ █ ███ █ █ █ ███", + "████▄▀▄▀█▄▀▄▄ ▀▄█▀▄▀▄███", + "██████ ▄▀▀▀▄▄██▀▀▄▀▄████", + "███████▄▄▀▀▄▄▄ ▀▄▄██████", + "███████████▄▄▄██████████", + "████████████████████████", + ]; + crate::output::ascii::assert_rasterised(&actual, &expected); + } +} diff --git a/crates/total-viewsheds/src/output/viewsheds/visible_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs similarity index 72% rename from crates/total-viewsheds/src/output/viewsheds/visible_polygon.rs rename to crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs index 3887360..bd68e7a 100644 --- a/crates/total-viewsheds/src/output/viewsheds/visible_polygon.rs +++ b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs @@ -1,17 +1,56 @@ //! Create euclidean polygons from polar segments. -/// `VisiblePolygon` -pub struct VisiblePolygon { +/// The 5 vertices of a segment and their distances from the centre. +#[derive(Debug)] +pub struct Vertices { + /// The 5 vertices of a segment. The 4 corners plus a copy of the first vertex to close the + /// polygon. + pub vertices: [geo::Coord; 5], + /// The distances of the top and bottom of the segment from the centre. + pub distances: std::ops::Range, +} + +/// `SegmentPolygon` +pub struct SegmentPolygon { /// Scale of DEM data. - pub scale: f32, + pub dem_scale: f32, /// The current sector angle. - pub current_angle: f32, + pub angle: f32, + /// The number of angles per degree. + pub angle_scale: f32, } -impl VisiblePolygon { +impl SegmentPolygon { + /// Make a single polygon representing a visible region of the planet. + pub fn make(&self, opening_index: u32, closing_index: u32) -> Vertices { + let opening_coord = self.index_to_coordinate(opening_index); + let closing_coord = self.index_to_coordinate(closing_index); + + let spread = 1.0f64 / f64::from(self.angle_scale) / 2.0f64; + let bottom_left = Self::rotate_by(opening_coord, spread); + let bottom_right = Self::rotate_by(opening_coord, -spread); + let top_left = Self::rotate_by(closing_coord, spread); + let top_right = Self::rotate_by(closing_coord, -spread); + + let scale = f64::from(self.dem_scale); + + let vertices = [ + bottom_left * scale, + bottom_right * scale, + top_right * scale, + top_left * scale, + bottom_left * scale, + ]; + + Vertices { + vertices, + distances: opening_index..closing_index, + } + } + /// Convert an index along a line of sight into a coordinate. fn index_to_coordinate(&self, index: u32) -> super::viewshed::Coordinate { - let radians = self.current_angle.to_radians(); + let radians = self.angle.to_radians(); let distance = f64::from(index); super::viewshed::Coordinate(geo::coord! { @@ -35,31 +74,6 @@ impl VisiblePolygon { y: dx * sin + dy * cos } } - - /// Make a single polygon representing a visible region of the planet. - pub fn make_visible_polygon(&self, opening_index: u32, closing_index: u32) -> geo::Polygon { - let opening_coord = self.index_to_coordinate(opening_index); - let closing_coord = self.index_to_coordinate(closing_index); - - let spread = 0.5001f64; - let bottom_left = Self::rotate_by(opening_coord, spread); - let bottom_right = Self::rotate_by(opening_coord, -spread); - let top_left = Self::rotate_by(closing_coord, spread); - let top_right = Self::rotate_by(closing_coord, -spread); - - let scale = f64::from(self.scale); - - geo::Polygon::new( - geo::LineString(vec![ - bottom_left * scale, - bottom_right * scale, - top_right * scale, - top_left * scale, - bottom_left * scale, - ]), - vec![], - ) - } } #[cfg(test)] @@ -67,10 +81,11 @@ mod test { fn builder( viewshed: &crate::output::viewsheds::viewshed::Viewshed, angle: f32, - ) -> crate::output::viewsheds::visible_polygon::VisiblePolygon { - crate::output::viewsheds::visible_polygon::VisiblePolygon { - scale: viewshed.dem.scale, - current_angle: angle, + ) -> crate::output::viewsheds::segment_polygon::SegmentPolygon { + crate::output::viewsheds::segment_polygon::SegmentPolygon { + dem_scale: viewshed.dem.scale, + angle, + angle_scale: 1.0, } } @@ -89,7 +104,10 @@ mod test { pov_coord: tvs_lib::dem::Coordinate(setup.pov), }; let viewsheder = builder(&viewshed, setup.angle); - let polygon = viewsheder.make_visible_polygon(setup.opening_index, setup.closing_index); + let vertices = viewsheder + .make(setup.opening_index, setup.closing_index) + .vertices; + let polygon = geo::Polygon::new(geo::LineString(vertices.into()), vec![]); let mut polygon_as_dem_coords = Vec::new(); for coord in &polygon.exterior().0 { @@ -145,11 +163,11 @@ mod test { closing_index: 2, }), vec![ - (4.7009067, 3.2867503), - (4.7132503, 3.2990939), - (5.4265022, 2.5981862), - (5.401815, 2.5734989), - (4.7009067, 3.2867503) + (4.7009079, 3.2867515), + (4.7132491, 3.2990927), + (5.4264998, 2.5981837), + (5.4018174, 2.5735014), + (4.7009079, 3.2867515) ] .into_iter() .map(Into::into) @@ -188,11 +206,11 @@ mod test { closing_index: 2, }), vec![ - (3.7132498, 5.7009093), - (3.7009061, 5.7132529), - (4.4018138, 6.4265049), - (4.4265011, 6.4018176), - (3.7132498, 5.7009093) + (3.7132486, 5.7009105), + (3.7009074, 5.7132517), + (4.4018163, 6.4265025), + (4.4264987, 6.4018201), + (3.7132486, 5.7009105) ] .into_iter() .map(Into::into) diff --git a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs index a085a82..9725f62 100644 --- a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs +++ b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs @@ -85,12 +85,8 @@ impl Viewshed<'_> { color_eyre::eyre::bail!("Point of view ({:?}) is not calculable", viewshed.pov_coord); } - let multi_polygon = super::visible_polygon::VisiblePolygon::parse_polar_segments( - &segments, - viewshed.dem.scale, - ); - - tracing::debug!("Viewshed reconstructed in {:?}.", start.elapsed()); + let multi_polygon = super::joiner_common::Joiner::build(&segments, viewshed.dem.scale)?; + tracing::info!("Viewshed reconstructed in {:?}.", start.elapsed()); Ok((pov_dem_coord, multi_polygon)) } @@ -190,7 +186,7 @@ impl Viewshed<'_> { #[cfg(test)] mod test { - use crate::output::ascii::assert_viewshed; + use crate::output::ascii::assert_rasterised; const SUMMIT_VIEWSHED: [&str; 12] = [ "████████████████████████", @@ -215,8 +211,8 @@ mod test { "██▀ █████████████ ▀█████", "██ ███████████████ █████", "██ ████████▀ ▄▄▄▄▄▄█████", - "██ ▀██████ ▄████████████", - "███ ▀█████ █████████████", + "██ ▀█▀▄ ██ ▄████████████", + "███ ▀▄▄███ █████████████", "████▄ ▀▀██ █████████████", "███████▄▄▄▄█████████████", "████████████████████████", @@ -248,7 +244,7 @@ mod test { "████████████████████████", "████████████████████████", ]; - assert_viewshed(&viewshed, expected); + assert_rasterised(&viewshed, expected); } #[test] @@ -262,11 +258,12 @@ mod test { ..crate::run::test::default_config(&temp_db) }, ); - assert_viewshed(&viewshed, &SUMMIT_VIEWSHED); + assert_rasterised(&viewshed, &SUMMIT_VIEWSHED); } #[test] fn viewshed_on_summit_with_high_angle_count() { + crate::setup_logging().unwrap(); let temp_db = tempfile::NamedTempFile::new().unwrap(); let viewshed = crate::output::ascii::make_viewshed( &crate::tests::fixtures::bigger_dem(), @@ -277,7 +274,7 @@ mod test { ..crate::run::test::default_config(&temp_db) }, ); - assert_viewshed(&viewshed, &PACMAN_VIEWSHED); + assert_rasterised(&viewshed, &PACMAN_VIEWSHED); } #[test] @@ -310,7 +307,7 @@ mod test { ..crate::run::test::default_config(&temp_db_for_viewsheds) }; - assert_viewshed( + assert_rasterised( &crate::output::ascii::make_viewshed( &elevations, geo::Coord { x: 5.0, y: 5.0 }, @@ -319,7 +316,7 @@ mod test { &SUMMIT_VIEWSHED, ); - assert_viewshed( + assert_rasterised( &crate::output::ascii::make_viewshed( &elevations, geo::Coord { x: 6.0, y: 6.0 }, @@ -328,7 +325,7 @@ mod test { &SUMMIT_VIEWSHED, ); - assert_viewshed( + assert_rasterised( &crate::output::ascii::make_viewshed( &elevations, geo::Coord { x: 7.0, y: 7.0 }, @@ -340,6 +337,7 @@ mod test { #[test] fn viewshed_near_summit() { + crate::setup_logging().unwrap(); let temp_db = tempfile::NamedTempFile::new().unwrap(); let viewshed = crate::output::ascii::make_viewshed( &crate::tests::fixtures::bigger_dem(), @@ -350,7 +348,7 @@ mod test { }, ); - assert_viewshed(&viewshed, &PACMAN_VIEWSHED); + assert_rasterised(&viewshed, &PACMAN_VIEWSHED); } #[test] @@ -380,6 +378,6 @@ mod test { "████████████████████████", "████████████████████████", ]; - assert_viewshed(&viewshed, expected); + assert_rasterised(&viewshed, expected); } } diff --git a/crates/total-viewsheds/src/storage/segments.rs b/crates/total-viewsheds/src/storage/segments.rs index a7f36d7..4f13906 100644 --- a/crates/total-viewsheds/src/storage/segments.rs +++ b/crates/total-viewsheds/src/storage/segments.rs @@ -8,7 +8,7 @@ pub struct Segment(pub u32); impl Segment { /// `new` creates a `Segment` the segment's start point and the distance - fn new(start: u16, distance: u16) -> Self { + pub fn new(start: u16, distance: u16) -> Self { // pack start/distsance into a u32 in the format of (start|distance) let wide_start: u32 = start.into(); let wide_distance: u32 = distance.into(); From 4f2574f7a34b8799eade945f36958861a76bc205 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Fri, 3 Jul 2026 15:06:27 +0700 Subject: [PATCH 2/7] refactor: explicit `pub` visibility Adds the following lints: unreachable_pub = "warn" redundant_pub_crate = "allow" This enforces the practice of explicitly defining visibility. Therefore, if a symbol isn't truly public to the outside world then it should be qualified with `pub(crate)`, `pub(super)`, etc. This is slightly more verbose but helps reasoning about API surfaces and also safeguards against unexpected symbols becoming public when parent visibility is changed. --- Cargo.toml | 6 + .../src/compute/area_of_interest.rs | 10 +- crates/total-viewsheds/src/compute/kernel.rs | 4 +- crates/total-viewsheds/src/compute/los.rs | 9 +- .../total-viewsheds/src/compute/rotation.rs | 2 +- crates/total-viewsheds/src/compute/rotator.rs | 10 +- .../src/compute/unrolled_los.rs | 6 +- .../src/compute/vector_intrinsics.rs | 8 +- crates/total-viewsheds/src/config.rs | 16 +- crates/total-viewsheds/src/dump_usage.rs | 2 +- crates/total-viewsheds/src/los_pack.rs | 14 +- crates/total-viewsheds/src/main.rs | 38 +++-- crates/total-viewsheds/src/output/ascii.rs | 11 +- .../total-viewsheds/src/output/bresenham.rs | 6 +- crates/total-viewsheds/src/output/png.rs | 2 +- crates/total-viewsheds/src/output/tiff.rs | 2 +- .../src/output/viewsheds/growable_polygon.rs | 53 ++++--- .../src/output/viewsheds/joiner.rs | 60 ++++++++ .../{joiner_final.rs => joiner/last.rs} | 54 +++---- .../{joiner_common.rs => joiner/normal.rs} | 141 +++++++----------- .../src/output/viewsheds/segment_polygon.rs | 6 +- .../src/output/viewsheds/viewshed.rs | 12 +- crates/total-viewsheds/src/post_process.rs | 2 +- crates/total-viewsheds/src/pre_process.rs | 12 +- crates/total-viewsheds/src/run.rs | 24 +-- crates/total-viewsheds/src/storage/db.rs | 15 +- crates/total-viewsheds/src/storage/engine.rs | 8 +- .../total-viewsheds/src/storage/segments.rs | 16 +- crates/total-viewsheds/src/storage/worker.rs | 10 +- crates/total-viewsheds/src/tests/fixtures.rs | 8 +- crates/total-viewsheds/src/tile.rs | 4 +- crates/total-viewsheds/src/workers.rs | 4 +- 32 files changed, 308 insertions(+), 267 deletions(-) create mode 100644 crates/total-viewsheds/src/output/viewsheds/joiner.rs rename crates/total-viewsheds/src/output/viewsheds/{joiner_final.rs => joiner/last.rs} (91%) rename crates/total-viewsheds/src/output/viewsheds/{joiner_common.rs => joiner/normal.rs} (88%) diff --git a/Cargo.toml b/Cargo.toml index 553e4cd..0ce23b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde_json = "1.0.143" [workspace.lints.rust] # It's always good to write as much documentation as possible missing_docs = "warn" +unreachable_pub = "warn" [workspace.lints.clippy] # `clippy::all` is already on by default. It implies the following: @@ -93,3 +94,8 @@ multiple_unsafe_ops_per_block = "allow" # i dont write docs with punctuation but you can still tell what im saying doc_paragraphs_missing_punctuation = "allow" + +# Along with `unreachable_pub = "warn"` this enforces the practice of explicitly defining visibility. +# Therefore, if a symbol isn't truly public to the outside world then it should be qualified with +# `pub(crate)`, `pub(super)`, etc. +redundant_pub_crate = "allow" diff --git a/crates/total-viewsheds/src/compute/area_of_interest.rs b/crates/total-viewsheds/src/compute/area_of_interest.rs index 9bbcdf4..b0b9a20 100644 --- a/crates/total-viewsheds/src/compute/area_of_interest.rs +++ b/crates/total-viewsheds/src/compute/area_of_interest.rs @@ -5,7 +5,7 @@ use color_eyre::Result; use geo::Contains as _; /// `Pruner` -pub struct Pruner { +pub(crate) struct Pruner { /// Width of the DEM in points width: u32, /// The DEM coordinates for the Area of Interest. @@ -14,12 +14,12 @@ pub struct Pruner { impl Pruner { /// Instantiate. - pub const fn new(width: u32, polygon: geo::Polygon) -> Self { + pub(crate) const fn new(width: u32, polygon: geo::Polygon) -> Self { Self { width, polygon } } /// Convert user-provided lon/lat coordinates to a polygon. - pub fn lonlat_coords_to_polygon( + pub(crate) fn lonlat_coords_to_polygon( points: Vec<(f32, f32)>, metadata: &tvs_lib::metadata::MetaData, ) -> Result { @@ -41,7 +41,7 @@ impl Pruner { } /// Can the given DEM ID be ignored in computations. - pub fn is_prunable(&self, dem_id: i64) -> bool { + pub(crate) fn is_prunable(&self, dem_id: i64) -> bool { let dem_coord = self.convert_dem_id_to_coord(dem_id); !self.polygon.contains(&dem_coord.0) } @@ -52,7 +52,7 @@ impl Pruner { reason = "We're only dealing with a max of the DEM's width" )] /// Convert a DEM 1D index to a 2D coordinate. - pub fn convert_dem_id_to_coord(&self, dem_id: i64) -> tvs_lib::dem::Coordinate { + pub(crate) fn convert_dem_id_to_coord(&self, dem_id: i64) -> tvs_lib::dem::Coordinate { let x = dem_id.rem_euclid(self.width.into()) as f64; let y = dem_id.div_euclid(self.width.into()) as f64; tvs_lib::dem::Coordinate(geo::coord! {x: x, y: y}) diff --git a/crates/total-viewsheds/src/compute/kernel.rs b/crates/total-viewsheds/src/compute/kernel.rs index 3522e43..26ce83b 100644 --- a/crates/total-viewsheds/src/compute/kernel.rs +++ b/crates/total-viewsheds/src/compute/kernel.rs @@ -7,7 +7,7 @@ use geo::HasDimensions as _; use itertools::izip; /// The data output by a single angle. -pub struct OutputData { +pub(crate) struct OutputData { /// The visibile surface area. pub surfaces: Vec, /// The longest lines of sight. @@ -57,7 +57,7 @@ const DEFAULT_UNROLL: usize = const { /// `kernel` will calculate the longest line of sight heatmap for a given angle and elevation map /// assuming that the maximum line of sight is `max_los` -pub fn kernel( +pub(crate) fn kernel( db_worker: &crate::storage::worker::Worker, elevation_map: &[i16], output: &mut OutputData, diff --git a/crates/total-viewsheds/src/compute/los.rs b/crates/total-viewsheds/src/compute/los.rs index fbda642..404fe68 100644 --- a/crates/total-viewsheds/src/compute/los.rs +++ b/crates/total-viewsheds/src/compute/los.rs @@ -1,6 +1,7 @@ /// `LineOfSight` abstracts the implementation of line of sight calculations to /// any "carry through" that can be materialized into a (f32, f32). -pub trait LineOfSight, LOS: Angle + PrefixMax + Accumulate> { +pub(crate) trait LineOfSight, LOS: Angle + PrefixMax + Accumulate> +{ /// `line_of_sight` calculates a line of sight for the given `pov_height` /// and outputs a triple of the surface area, longest line of sight in meters /// and a vector of bools of which @@ -9,7 +10,7 @@ pub trait LineOfSight, LOS: Angle + PrefixMax + Accumul /// `Angle` abstracts the angle calculation between a pov and all the elevation data within /// its band of sight -pub trait Angle { +pub(crate) trait Angle { /// `calculate_angles` calculates the angle from the `pov_height` to a given elevation fn calculate_angles( pov_height: f32, @@ -23,7 +24,7 @@ pub trait Angle { /// `Accumulate` accumulates the surface area visible and longest line of sight /// in a pair of (f32, f32). `Accumulate` doesn't care about the implementation details /// of accumulation so long as the `Output` type can be materialized to (f32, f32) -pub trait Accumulate> { +pub(crate) trait Accumulate> { /// `accumulate` accumulates the surface area using the distances by comparing /// whether a point at a distance is visible (angle > prefix) /// If `output_sector` is true, it should output a bitmap of which distances are visible @@ -38,7 +39,7 @@ pub trait Accumulate> { } /// `PrefixMax` calculates the prefix maximum of the given angles -pub trait PrefixMax { +pub(crate) trait PrefixMax { /// `prefix_max` calculates the prefix max of the fn prefix_max(highest: f32, angles_in: &[f32], angles_out: &mut [f32]); } diff --git a/crates/total-viewsheds/src/compute/rotation.rs b/crates/total-viewsheds/src/compute/rotation.rs index b291b85..dcb17ed 100644 --- a/crates/total-viewsheds/src/compute/rotation.rs +++ b/crates/total-viewsheds/src/compute/rotation.rs @@ -14,7 +14,7 @@ use std::rc::Rc; reason = "rotation should not be out of bounds" )] #[expect(clippy::expect_used, reason = "invriants broken if options not none")] -pub fn lines( +pub(crate) fn lines( elevs: &[i16], max_los: usize, angle: f64, diff --git a/crates/total-viewsheds/src/compute/rotator.rs b/crates/total-viewsheds/src/compute/rotator.rs index b3b9190..cb42478 100644 --- a/crates/total-viewsheds/src/compute/rotator.rs +++ b/crates/total-viewsheds/src/compute/rotator.rs @@ -10,7 +10,7 @@ /// * 180°: (`width_index` - x, `width_index` - y) // * 270°: (y, `width_index` - x) #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Quadrant { +pub(crate) enum Quadrant { /// Top-right quadrant: 0° to 90° TopRight, /// Top-left quadrant: 90° to 180° @@ -55,7 +55,7 @@ impl From for Quadrant { } /// Rotator struct. -pub struct Rotator { +pub(crate) struct Rotator { /// The centre of the DEM in relative coordinates. centre: (f64, f64), /// The index of the last point in a row/column of the DEM. @@ -72,7 +72,7 @@ pub struct Rotator { impl Rotator { /// Instantiate. - pub fn new(angle: f64, width: isize) -> Self { + pub(crate) fn new(angle: f64, width: isize) -> Self { assert!( (0.0f64..360.0f64).contains(&angle), "Angle {angle} must be in range 0..360" @@ -109,7 +109,7 @@ impl Rotator { /// we'd just make the angle negative here. But seeing as the rest of the maths involved in /// skew rotation is based in positive angles it's better to achieve the same rotation /// through a positive but inverted angle. - pub fn invert_angle(angle: f64) -> f64 { + pub(crate) fn invert_angle(angle: f64) -> f64 { if angle > 0.0f64 { 360.0f64 - angle } else { @@ -122,7 +122,7 @@ impl Rotator { /// /// Note that each shear step _must_ be rounded. This is essential to achieving a bijective /// mapping. - pub fn rotate(&self, x: isize, y: isize) -> (isize, isize) { + pub(crate) fn rotate(&self, x: isize, y: isize) -> (isize, isize) { let (x_centre, y_centre) = self.centre; let (quadrant_x, quadrant_y) = self.rotate_to_quadrant(x, y); diff --git a/crates/total-viewsheds/src/compute/unrolled_los.rs b/crates/total-viewsheds/src/compute/unrolled_los.rs index b77ddc7..de4ab7c 100644 --- a/crates/total-viewsheds/src/compute/unrolled_los.rs +++ b/crates/total-viewsheds/src/compute/unrolled_los.rs @@ -27,7 +27,7 @@ fn generate_distances(max_los: usize, refraction: f32, scale: f32) -> (Vec, /// Unroll holds an unrolled heatmap and unrolled longest line of sight calculation /// Since in Line of Sight-land max/addition are commutative, then Unroll will be materialized /// into (f32, f32) -pub struct UnrollVector +pub(crate) struct UnrollVector where [(); UNROLL * VECTOR_WIDTH]:, { @@ -40,7 +40,7 @@ where } /// `UnrolledLOS` implements an Unrolled `LineOfSight` calculation -pub struct UnrolledVectorLos { +pub(crate) struct UnrolledVectorLos { /// `angles` holds a buffer for line of sight angles to be put into /// which is exactly `max_los+1` long angles: Vec, @@ -81,7 +81,7 @@ where impl UnrolledVectorLos { /// `new` initializes a new `UnrolledLOS`, and precalculates all the distances /// and earth curvature adjustments - pub fn new(max_los: usize, refraction: f32, scale: f32) -> Self { + pub(crate) fn new(max_los: usize, refraction: f32, scale: f32) -> Self { assert_eq!( max_los % VECTOR_WIDTH, 0, diff --git a/crates/total-viewsheds/src/compute/vector_intrinsics.rs b/crates/total-viewsheds/src/compute/vector_intrinsics.rs index b7e4183..a854ee1 100644 --- a/crates/total-viewsheds/src/compute/vector_intrinsics.rs +++ b/crates/total-viewsheds/src/compute/vector_intrinsics.rs @@ -7,7 +7,7 @@ use std::simd::{Mask, Simd, f32x4}; /// `VectorMax` performs an element-wise SIMD max of floats, allowing for architecture /// specific implementations -pub trait VectorMax { +pub(crate) trait VectorMax { /// `max` computes an element-wise maximum from lhs and rhs, assuming neither contain NaNs /// or -0.0 fn max(lhs: Simd, rhs: Simd) -> Simd; @@ -15,7 +15,7 @@ pub trait VectorMax { /// `VectorGreater` performs a SIMD greater than of floats, allowing for architecture /// specific implementations -pub trait VectorGreater { +pub(crate) trait VectorGreater { /// gt computes an element-wise maximum from lhs and rhs, assuming neither contain NaNs /// or -0.0 fn gt(lhs: Simd, rhs: Simd) -> Mask; @@ -23,7 +23,7 @@ pub trait VectorGreater { /// `VectorLos` is an implementation of the internals of `PrefixMax`, `Angle`, and `Accumulate` /// for Portable SIMD -pub struct VectorLos; +pub(crate) struct VectorLos; impl VectorMax for VectorLos { #[inline] @@ -333,7 +333,7 @@ impl Angle for VectorLos { /// `DEFAULT_VECTOR_LENGTH` determines the CPU Kernel's default vector length based off /// the architecture that the binary is built for -pub const DEFAULT_VECTOR_LENGTH: usize = const { +pub(crate) const DEFAULT_VECTOR_LENGTH: usize = const { if cfg!(target_feature = "avx512f") { 16 } else if cfg!(any(test, feature = "ring_data")) { diff --git a/crates/total-viewsheds/src/config.rs b/crates/total-viewsheds/src/config.rs index a249c07..b7fd5a5 100644 --- a/crates/total-viewsheds/src/config.rs +++ b/crates/total-viewsheds/src/config.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; #[command( about = "Generate _all_ the viewsheds for a given Digital Elevation Model, therefore the total viewsheds." )] -pub struct Config { +pub(crate) struct Config { #[command(subcommand)] /// The subcommand. pub command: Commands, @@ -18,7 +18,7 @@ pub struct Config { /// CLI subcommand. #[derive(clap::Subcommand, Debug)] -pub enum Commands { +pub(crate) enum Commands { /// Run main computations. Compute(Compute), /// Reconstruct a viewshed. @@ -35,7 +35,7 @@ pub enum Commands { /// Arguments to the `compute` subcommand. #[derive(clap::Parser, Debug, Default)] -pub struct Compute { +pub(crate) struct Compute { // TODO: make this "reserved rings" and add support to the kernel so that the user can get // feedback of the actual number needed. // @@ -154,7 +154,7 @@ pub struct Compute { /// Arguments to the `viewshed` subcommand. #[derive(clap::Parser, Debug)] -pub struct Viewshed { +pub(crate) struct Viewshed { /// Path or directory where viewshed DB was saved. #[arg(value_name = "Path to existing database")] pub db_path: std::path::PathBuf, @@ -170,7 +170,7 @@ pub struct Viewshed { /// Arguments to the `post-process` subcommand. #[derive(clap::Parser, Debug)] -pub struct PostProcess { +pub(crate) struct PostProcess { /// Directory where viewshed DBs were saved. #[arg(value_name = "Path to existing databases")] pub db_dir: std::path::PathBuf, @@ -211,7 +211,7 @@ fn is_perfect_square(argument: &str) -> Result { /// Where to run the computations. #[derive(clap::ValueEnum, Clone, Debug, Default)] -pub enum Backend { +pub(crate) enum Backend { /// Optimised cache-efficient CPU kernel #[default] CPU, @@ -221,7 +221,7 @@ pub enum Backend { /// Which calculations to process. #[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq)] -pub enum Process { +pub(crate) enum Process { /// Calculate everything. All, /// Compute all the polar segments saving them to disk so that they can be used to later @@ -231,7 +231,7 @@ pub enum Process { /// Where to run the computations. #[derive(clap::ValueEnum, Clone, Debug, Copy, Default)] -pub enum HeatmapNormalisation { +pub(crate) enum HeatmapNormalisation { /// Just scale between 0 and 1 UnitScale, /// Scale between 0 and 1 with an exponential factor. diff --git a/crates/total-viewsheds/src/dump_usage.rs b/crates/total-viewsheds/src/dump_usage.rs index 55bc641..1527ac3 100644 --- a/crates/total-viewsheds/src/dump_usage.rs +++ b/crates/total-viewsheds/src/dump_usage.rs @@ -5,7 +5,7 @@ use color_eyre::Result; /// main dump usage function #[expect(clippy::print_stdout, reason = "We need to output the usage.")] -pub fn dump_full_usage_for_readme() -> Result<()> { +pub(crate) fn dump_full_usage_for_readme() -> Result<()> { use clap::CommandFactory as _; let mut command = crate::config::Config::command(); diff --git a/crates/total-viewsheds/src/los_pack.rs b/crates/total-viewsheds/src/los_pack.rs index 3916bab..287e810 100644 --- a/crates/total-viewsheds/src/los_pack.rs +++ b/crates/total-viewsheds/src/los_pack.rs @@ -10,7 +10,7 @@ const U10_MAX: u32 = (1 << 10) - 1; #[derive(Default, Debug, Clone, Copy)] /// Line of sight data that can be packed within 32 bits. -pub struct LineOfSightPacked(f32); +pub(crate) struct LineOfSightPacked(f32); #[expect( clippy::big_endian_bytes, @@ -21,7 +21,7 @@ impl LineOfSightPacked { /// /// f32 is better than u32 because I imagine that floats are just more widely recognised in general. /// At the end of the day, they're just bits that are neither valid f32 nor u32. - pub fn new(distance: u32, angle: u16) -> Result { + pub(crate) fn new(distance: u32, angle: u16) -> Result { if distance > U22_MAX { color_eyre::eyre::bail!("{} is greater than u22 max {U22_MAX}", distance); } @@ -37,7 +37,7 @@ impl LineOfSightPacked { /// create a packed line of sight without checking if the values fit within /// the packed bit widths - pub unsafe fn new_unchecked(distance: u32, angle: u16) -> Self { + pub(crate) unsafe fn new_unchecked(distance: u32, angle: u16) -> Self { let angle_u32 = u32::from(angle); let distance_bits = (distance & U22_MAX) << 10u32; @@ -54,25 +54,25 @@ impl LineOfSightPacked { } /// Distance in meters from the point of view to the visible point. - pub const fn distance(self) -> u32 { + pub(crate) const fn distance(self) -> u32 { (self.to_u32() >> 10u32) & U22_MAX } /// The angle of the line of sight from the point of view. #[expect(clippy::as_conversions, reason = "(self & 2^10) < 2^16")] - pub const fn angle(self) -> u16 { + pub(crate) const fn angle(self) -> u16 { (self.to_u32() & U10_MAX) as u16 } /// Get the raw f32 value. It doesn't represent any useful data, it's just the f32 view of the /// packed data. - pub const fn as_f32(self) -> f32 { + pub(crate) const fn as_f32(self) -> f32 { self.0 } /// calculate the longer packed line of sight, and when distances are equal /// choose the smaller angle - pub const fn max(self, rhs: Self) -> Self { + pub(crate) const fn max(self, rhs: Self) -> Self { if rhs.distance() > self.distance() { return rhs; } diff --git a/crates/total-viewsheds/src/main.rs b/crates/total-viewsheds/src/main.rs index dfedfa7..8bb6314 100644 --- a/crates/total-viewsheds/src/main.rs +++ b/crates/total-viewsheds/src/main.rs @@ -2,12 +2,11 @@ #![feature(portable_simd)] #![feature(specialization)] #![feature(mpmc_channel)] +#![feature(generic_const_exprs)] #![expect( incomplete_features, reason = "our usage isn't crazy and unlikely to break" )] -#![feature(generic_const_exprs)] -#![expect(clippy::pub_use, reason = "I admit I don't understand the other way.")] #![cfg_attr( test, expect( @@ -40,7 +39,7 @@ mod workers; /// cpu implements a CPU kernel for the longest line of sight mod compute { - pub mod area_of_interest; + pub(crate) mod area_of_interest; /// los contains all the traits necessary for implementing a line of sight algorithm mod los; @@ -49,7 +48,7 @@ mod compute { mod rotator; /// kernel is the exported kernel module - pub mod kernel; + pub(crate) mod kernel; /// `unrolled_los` holds a fully implemented los trait for unrolled vectorization mod unrolled_los; @@ -57,31 +56,30 @@ mod compute { /// `vector_intrinsics` holds all the vector-related LOS intrinsics mod vector_intrinsics; - pub use kernel::kernel; + pub(crate) use kernel::kernel; } /// Database for viewsheds mod storage { - pub mod db; - pub mod engine; - pub mod segments; - pub mod worker; + pub(crate) mod db; + pub(crate) mod engine; + pub(crate) mod segments; + pub(crate) mod worker; } /// Various ways to output data. mod output { - pub mod ascii; - pub mod bresenham; - pub mod png; - pub mod tiff; + mod ascii; + mod bresenham; + pub(crate) mod png; + pub(crate) mod tiff; /// Load, parse and reconstruct euclidean polygon viewsheds from their raw polar segments. - pub mod viewsheds { - pub mod growable_polygon; - pub mod joiner_common; - pub mod joiner_final; - pub mod segment_polygon; - pub mod viewshed; + pub(crate) mod viewsheds { + mod growable_polygon; + mod joiner; + mod segment_polygon; + pub(crate) mod viewshed; } } @@ -209,5 +207,5 @@ fn compute(config: &config::Compute) -> Result<()> { #[cfg(test)] mod tests { - pub mod fixtures; + pub(crate) mod fixtures; } diff --git a/crates/total-viewsheds/src/output/ascii.rs b/crates/total-viewsheds/src/output/ascii.rs index fb6f72e..d451f08 100644 --- a/crates/total-viewsheds/src/output/ascii.rs +++ b/crates/total-viewsheds/src/output/ascii.rs @@ -7,7 +7,7 @@ use geo::CoordsIter as _; use crate::output::viewsheds::viewshed::Viewshed; -pub fn make_viewshed( +pub(crate) fn make_viewshed( elevations: &[i16], viewshed_pov: geo::Coord, config: crate::run::Config, @@ -57,7 +57,10 @@ pub fn make_viewshed( rasterise_multi_polygon(&viewshed, dem.width) } -pub fn rasterise_multi_polygon(multi_polygon: &geo::MultiPolygon, width: u32) -> Vec { +pub(crate) fn rasterise_multi_polygon( + multi_polygon: &geo::MultiPolygon, + width: u32, +) -> Vec { let scale = std::env::var("ASCII_TEST_SIZE") .unwrap_or_else(|_| "2".to_owned()) .parse::() @@ -164,7 +167,7 @@ pub fn rasterise_multi_polygon(multi_polygon: &geo::MultiPolygon, width: u32) -> ascii } -pub fn rasterise(mut multi_polygon: geo::MultiPolygon) -> Vec { +pub(crate) fn rasterise(mut multi_polygon: geo::MultiPolygon) -> Vec { let width = 12u32; let centre = f64::from(width.div_euclid(2)); for polygons in multi_polygon.iter_mut() { @@ -192,7 +195,7 @@ pub fn rasterise(mut multi_polygon: geo::MultiPolygon) -> Vec { } #[expect(clippy::print_stderr, reason = "This is for tests")] -pub fn assert_rasterised(actual: &[String], expected: &[&str]) { +pub(crate) fn assert_rasterised(actual: &[String], expected: &[&str]) { if actual != expected { eprintln!("Actual:"); eprint!("{}", actual.join("\n")); diff --git a/crates/total-viewsheds/src/output/bresenham.rs b/crates/total-viewsheds/src/output/bresenham.rs index f4e49dc..7c9109c 100644 --- a/crates/total-viewsheds/src/output/bresenham.rs +++ b/crates/total-viewsheds/src/output/bresenham.rs @@ -2,7 +2,7 @@ #[derive(Clone, Copy, Debug)] /// A raster coodinate. -pub struct RasterCoord { +pub(crate) struct RasterCoord { /// The x coordinate. pub x: i32, /// The y coordinate. @@ -20,7 +20,7 @@ impl From<(i32, i32)> for RasterCoord { /// Iterator that yields (x, y) pixels on a line using Bresenham's algorithm. /// -pub struct Bresenham { +pub(crate) struct Bresenham { /// The current start of the line. Updates as we move through the rasterisation. from: RasterCoord, /// Where the line ends. @@ -38,7 +38,7 @@ pub struct Bresenham { impl Bresenham { /// Instantiate. - pub fn new(from: RasterCoord, to: RasterCoord) -> Self { + pub(crate) fn new(from: RasterCoord, to: RasterCoord) -> Self { let delta = RasterCoord { x: (to.x - from.x).abs(), y: (to.y - from.y).abs(), diff --git a/crates/total-viewsheds/src/output/png.rs b/crates/total-viewsheds/src/output/png.rs index 238189e..4215268 100644 --- a/crates/total-viewsheds/src/output/png.rs +++ b/crates/total-viewsheds/src/output/png.rs @@ -2,7 +2,7 @@ use color_eyre::{Result, eyre::ContextCompat as _}; /// Convert an array of floats to a grayscale heatmap. -pub fn save( +pub(crate) fn save( data: &[f32], width: u32, height: u32, diff --git a/crates/total-viewsheds/src/output/tiff.rs b/crates/total-viewsheds/src/output/tiff.rs index 1de965d..9213b15 100644 --- a/crates/total-viewsheds/src/output/tiff.rs +++ b/crates/total-viewsheds/src/output/tiff.rs @@ -3,7 +3,7 @@ use color_eyre::Result; /// Save an array of `f32`s (total surfaces, longest lines of sight) to a `.tiff` file. -pub fn save(dem: &tvs_lib::dem::DEM, data: &[f32], path: &std::path::PathBuf) -> Result<()> { +pub(crate) fn save(dem: &tvs_lib::dem::DEM, data: &[f32], path: &std::path::PathBuf) -> Result<()> { let driver = gdal::DriverManager::get_driver_by_name("GTiff")?; let mut dataset = driver.create_with_band_type::( diff --git a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs index 995c314..1edf30d 100644 --- a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs +++ b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs @@ -6,7 +6,7 @@ use color_eyre::eyre::{ContextCompat as _, Result, bail}; /// The `u32` values in the variants are for storing the polar distance from the centre. This saves /// having to do trigonometry to figure out if two openings are touching. #[derive(Eq, PartialEq, Debug, Clone)] -pub enum Opening { +pub(crate) enum Opening { /// No opening. We could also just use `Option::None`, but unwrapping it isn't so ergonomic. Null, /// This i for polygons that start at 0 degrees. It's possible that another polygon (or even @@ -29,7 +29,7 @@ pub enum Opening { /// A vertex is a single point in a polygon. #[derive(Debug, Clone)] -pub struct Vertex { +pub(crate) struct Vertex { /// The coordinate of the vertex in euclidian space. pub coordinate: geo::Coord, /// The kind of opening that this vertex represents. @@ -39,14 +39,14 @@ pub struct Vertex { impl Vertex { /// Is the vertex at the centre of the viewshed? The centre is a special place, it is a /// zero-length opening that isn't nulled if no segments/polygons touch it for a given angle. - pub fn is_centre(&self) -> bool { + pub(crate) fn is_centre(&self) -> bool { self.coordinate.x.abs() == 0.0 && self.coordinate.y.abs() == 0.0 } } /// A polygon that grows, but only from its anti-clockwise facing side. #[derive(Debug, Clone)] -pub struct GrowablePolygon { +pub(crate) struct GrowablePolygon { /// The main exterior vertices. pub vertices: Vec, /// Interiore holes within the polygon. @@ -66,7 +66,10 @@ pub struct GrowablePolygon { impl GrowablePolygon { /// Create a new growable polygon. It always begins with a single polar segment converted to /// its euclidean coordinates. - pub fn new(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { + pub(crate) fn new( + segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, + angle: f32, + ) -> Self { let mut vertices = Vec::new(); for (segment_index, segment_vertex) in segment_vertices.vertices.iter().enumerate() { let opening = match segment_index { @@ -117,7 +120,10 @@ impl GrowablePolygon { /// w└──┘x w└──┘x /// /// (abcde) + (wxyz) = (wxyzcdeb) - fn new_for_insertion(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { + fn new_for_insertion( + segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, + angle: f32, + ) -> Self { let mut polygon = Self::new(segment_vertices, angle); #[expect( @@ -140,18 +146,16 @@ impl GrowablePolygon { /// polygon as follows: /// * `Opening::NewStart/NewEnd` become `Opening::Start/End`. /// * `Opening::Start/End` become `Opening::Null`. - pub fn downgrade_openings(&mut self) -> Result<()> { + pub(crate) fn downgrade_openings(&mut self) -> Result<()> { tracing::trace!("Downgrading openings"); let mut iterator = self.vertices.iter_mut().rev(); while let Some(vertex) = iterator.next() { match vertex.opening { - super::growable_polygon::Opening::Null - | super::growable_polygon::Opening::GenesisStart(_) - | super::growable_polygon::Opening::GenesisEnd(_) => {} - super::growable_polygon::Opening::End(_) => { + Opening::Null | Opening::GenesisStart(_) | Opening::GenesisEnd(_) => {} + Opening::End(_) => { bail!("Dangling `Opening`"); } - super::growable_polygon::Opening::Start(_) => { + Opening::Start(_) => { let Some(next) = iterator.next() else { bail!("`Opening::Start` without adjacent end"); }; @@ -159,10 +163,10 @@ impl GrowablePolygon { vertex.opening = Opening::Null; next.opening = Opening::Null; } - super::growable_polygon::Opening::NewStart(at) => { + Opening::NewStart(at) => { vertex.opening = Opening::Start(at); } - super::growable_polygon::Opening::NewEnd(at) => { + Opening::NewEnd(at) => { vertex.opening = Opening::End(at); } } @@ -201,9 +205,9 @@ impl GrowablePolygon { } /// Insert a segment into the polygon. - pub fn join_segment( + pub(crate) fn join_segment( &mut self, - segment_vertices: &super::segment_polygon::Vertices, + segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, vertices_range: std::ops::Range, angle: f32, ) -> Result<()> { @@ -239,7 +243,7 @@ impl GrowablePolygon { /// We know that a segment always has `NewEnd` and `NewStart` openings. Therefore we can /// calculate the index "x" at which the new polygon should be inserted by finding `NewEnd` and /// substracting 1. - pub fn join_non_starting_polygon( + pub(crate) fn join_non_starting_polygon( &mut self, base_vertices_range: std::ops::Range, joining_polygon: &mut Self, @@ -292,7 +296,7 @@ impl GrowablePolygon { } /// Insert a starting polygon (from angle 0) into a final polygon (from angle ~360). - pub fn join_starting_polygon( + pub(crate) fn join_starting_polygon( &mut self, base_vertices_range: std::ops::Range, joining_vertices_range: std::ops::Range, @@ -330,7 +334,7 @@ impl GrowablePolygon { } /// Join a polygon into itself. - pub fn join_self( + pub(crate) fn join_self( &mut self, left_range: std::ops::Range, right_range: std::ops::Range, @@ -350,7 +354,7 @@ impl GrowablePolygon { } /// Extract the starting vertex of an opening that has just been joined to. - pub fn extract_old_start(&mut self, index: usize) -> Result { + pub(crate) fn extract_old_start(&mut self, index: usize) -> Result { let vertex = self .vertices .get_mut(index) @@ -376,12 +380,15 @@ impl GrowablePolygon { /// Is a segment and a polygon, or 2 polygons, touching? We decide by whether their openings are /// overlapping. - pub const fn is_touching(left: &std::ops::Range, right: &std::ops::Range) -> bool { + pub(crate) const fn is_touching( + left: &std::ops::Range, + right: &std::ops::Range, + ) -> bool { left.start < right.end && right.start < left.end } /// Convert the polygon to the `geo` crate's representation. Ready for exporting to `GeoJSON`. - pub fn to_geo_polygon(&self) -> geo::Polygon { + pub(crate) fn to_geo_polygon(&self) -> geo::Polygon { let holes: Vec = self .holes .iter() @@ -417,7 +424,7 @@ impl GrowablePolygon { /// Dedupe vertices, but allow destroying opening metadata. This is useful right at the very end /// of the reconstruction. - pub fn dedup_vertices_ignore_openings(&mut self) { + pub(crate) fn dedup_vertices_ignore_openings(&mut self) { self.vertices.dedup_by(|left, right| { Self::are_coordinates_within_tolerance(&left.coordinate, &right.coordinate) }); diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner.rs b/crates/total-viewsheds/src/output/viewsheds/joiner.rs new file mode 100644 index 0000000..38abecb --- /dev/null +++ b/crates/total-viewsheds/src/output/viewsheds/joiner.rs @@ -0,0 +1,60 @@ +//! Join segments and polygons to existing growable polygons. + +mod last; +mod normal; + +use color_eyre::Result; + +/// Keeps track of active and completed polygons within a viewshed. +#[derive(Default)] +struct Joiner { + /// Polygons that don't intersect with the current angle. They don't need to be checked against + /// new segments. + completed: Vec, + /// Polygons that intersect with the current angle. They must be checked to see if any of their + /// openings touch any of the segments of the current angle. + active: Vec, +} + +/// Build a viewshed of euclidean polygons from polar segments. +pub(crate) fn build( + data: &[Vec], + dem_scale: f32, +) -> Result { + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "The angle count should never strain the f32 mantissa" + )] + let angle_count = data.len() as f32; + let angle_scale = angle_count / 360.0; + + let mut joiner = Joiner { + completed: Vec::new(), + active: Vec::new(), + }; + + for (anglish, segments) in data.iter().enumerate() { + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "The angle count should never strain the f32 mantissa" + )] + let angle = anglish as f32 / angle_scale; + + joiner.build_angle(angle, angle_scale, segments, dem_scale)?; + } + + joiner.build_final_angle()?; + joiner.move_all_active_polygons_to_completed(); + + let mut geo_polygons = Vec::new(); + for mut raw_polygon in joiner.completed { + raw_polygon.dedup_vertices_ignore_openings(); + let polygon = raw_polygon.to_geo_polygon(); + tracing::trace!("Final viewshed, adding polygon: {polygon:?}"); + geo_polygons.push(polygon); + } + + Ok(geo::MultiPolygon::new(geo_polygons)) +} diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner_final.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs similarity index 91% rename from crates/total-viewsheds/src/output/viewsheds/joiner_final.rs rename to crates/total-viewsheds/src/output/viewsheds/joiner/last.rs index 97c0ada..7b17047 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner_final.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs @@ -5,7 +5,9 @@ use color_eyre::eyre::{ContextCompat as _, Result, bail}; use itertools::Itertools as _; -impl super::joiner_common::Joiner { +use crate::output::viewsheds::growable_polygon::Opening; + +impl super::Joiner { /// The final angle faces the challenge of joining polygons together from the first angle. pub(crate) fn build_final_angle(&mut self) -> Result<()> { tracing::trace!("Building viewshed for final angle"); @@ -101,19 +103,19 @@ impl super::joiner_common::Joiner { let mut iterator = vertices_clone.iter().enumerate().rev(); while let Some((index, vertex)) = iterator.next() { match vertex.opening { - super::growable_polygon::Opening::Null - | super::growable_polygon::Opening::NewStart(_) - | super::growable_polygon::Opening::NewEnd(_) - | super::growable_polygon::Opening::Start(_) - | super::growable_polygon::Opening::End(_) => (), - super::growable_polygon::Opening::GenesisStart(_) => { + Opening::Null + | Opening::NewStart(_) + | Opening::NewEnd(_) + | Opening::Start(_) + | Opening::End(_) => (), + Opening::GenesisStart(_) => { bail!("Dangling `Opening::GenesisStart`"); } - super::growable_polygon::Opening::GenesisEnd(start) => { + Opening::GenesisEnd(start) => { let Some(next) = iterator.next() else { bail!("`Opening::GenesisEnd` without a following vertex"); }; - let super::growable_polygon::Opening::GenesisStart(end) = next.1.opening else { + let Opening::GenesisStart(end) = next.1.opening else { tracing::error!( "Bad opening ({:?}) in polygon: {vertices_clone:#?}", next.1.opening @@ -182,20 +184,19 @@ impl super::joiner_common::Joiner { let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); while let Some((index, vertex)) = iterator.next() { match vertex.opening { - super::growable_polygon::Opening::Start(opening_start) => { + Opening::Start(opening_start) => { let Some((index_next, vertex_next)) = iterator.next() else { bail!("Opening without following vertex"); }; - let super::growable_polygon::Opening::End(opening_end) = vertex_next.opening - else { + let Opening::End(opening_end) = vertex_next.opening else { bail!("Opening `Start` not followed by opening `End`"); }; let base_opening_range = opening_start..opening_end; tracing::trace!("Checking touching for vertex ({:?})...", vertex); - if super::growable_polygon::GrowablePolygon::is_touching( + if crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( &base_opening_range, &joining_opening_range, ) { @@ -215,14 +216,14 @@ impl super::joiner_common::Joiner { ); } } - super::growable_polygon::Opening::End(_) => { + Opening::End(_) => { bail!("Unexpected opening `End` reached"); } - super::growable_polygon::Opening::Null - | super::growable_polygon::Opening::GenesisStart(_) - | super::growable_polygon::Opening::GenesisEnd(_) - | super::growable_polygon::Opening::NewStart(_) - | super::growable_polygon::Opening::NewEnd(_) => (), + Opening::Null + | Opening::GenesisStart(_) + | Opening::GenesisEnd(_) + | Opening::NewStart(_) + | Opening::NewEnd(_) => (), } } @@ -260,12 +261,11 @@ impl super::joiner_common::Joiner { #[cfg(test)] mod test { - #[test] fn final_segment_joins_starting_segment_simple() { crate::setup_logging().unwrap(); let part = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[part.clone(), vec![], vec![], vec![], vec![], part], 1.0, ) @@ -291,7 +291,7 @@ mod test { #[test] fn inherit_holes_final() { crate::setup_logging().unwrap(); - let joined = crate::output::viewsheds::joiner_common::Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ vec![crate::storage::segments::Segment::new(0, 3)], vec![ @@ -332,7 +332,7 @@ mod test { crate::storage::segments::Segment::new(0, 1), crate::storage::segments::Segment::new(2, 1), ]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[starting, vec![], vec![], vec![], vec![], long], 1.0, ) @@ -363,7 +363,7 @@ mod test { crate::storage::segments::Segment::new(2, 1), crate::storage::segments::Segment::new(4, 1), ]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[starting, long.clone(), vec![], vec![], vec![], long], 1.0, ) @@ -391,7 +391,7 @@ mod test { crate::setup_logging().unwrap(); let long = vec![crate::storage::segments::Segment::new(0, 4)]; let short = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ short.clone(), long.clone(), @@ -428,7 +428,7 @@ mod test { crate::setup_logging().unwrap(); let part = vec![crate::storage::segments::Segment::new(2, 2)]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build(&ring, 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build(&ring, 1.0).unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -455,7 +455,7 @@ mod test { crate::storage::segments::Segment::new(3, 1), ]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner_common::Joiner::build(&ring, 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build(&ring, 1.0).unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner_common.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs similarity index 88% rename from crates/total-viewsheds/src/output/viewsheds/joiner_common.rs rename to crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs index 2fa1de3..22b8396 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner_common.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs @@ -1,66 +1,14 @@ -//! Join the visible segments from each computed angle into the viewshed. These "common" joinings -//! are the ones the occur for every angle other than the final one. Therefore these should simpler +//! Join the visible segments from each computed angle into the viewshed. These "normal" joinings +//! are the ones that occur for every angle other than the final one. Therefore these should simpler //! and faster. use color_eyre::eyre::{Result, bail}; -/// Keeps track of active and completed polygons within a viewshed. -#[derive(Default)] -pub struct Joiner { - /// Polygons that don't intersect with the current angle. They don't need to be checked against - /// new segments. - pub completed: Vec, - /// Polygons that intersect with the current angle. They must be checked to see if any of their - /// openings touch any of the segments of the current angle. - pub active: Vec, -} - -impl Joiner { - /// Build a viewshed of euclidean polygons from polar segments. - pub fn build( - data: &[Vec], - dem_scale: f32, - ) -> Result { - #[expect( - clippy::as_conversions, - clippy::cast_precision_loss, - reason = "The angle count should never strain the f32 mantissa" - )] - let angle_count = data.len() as f32; - let angle_scale = angle_count / 360.0; - - let mut joiner = Self { - completed: Vec::new(), - active: Vec::new(), - }; - - for (anglish, segments) in data.iter().enumerate() { - #[expect( - clippy::as_conversions, - clippy::cast_precision_loss, - reason = "The angle count should never strain the f32 mantissa" - )] - let angle = anglish as f32 / angle_scale; - - joiner.build_angle(angle, angle_scale, segments, dem_scale)?; - } - - joiner.build_final_angle()?; - joiner.move_all_active_polygons_to_completed(); - - let mut geo_polygons = Vec::new(); - for mut raw_polygon in joiner.completed { - raw_polygon.dedup_vertices_ignore_openings(); - let polygon = raw_polygon.to_geo_polygon(); - tracing::trace!("Final viewshed, adding polygon: {polygon:?}"); - geo_polygons.push(polygon); - } - - Ok(geo::MultiPolygon::new(geo_polygons)) - } +use crate::output::viewsheds::growable_polygon::Opening; +impl super::Joiner { /// Build the viewshed for a single angle. - fn build_angle( + pub(crate) fn build_angle( &mut self, angle: f32, angle_scale: f32, @@ -80,7 +28,7 @@ impl Joiner { let mut new_polygons = Vec::new(); - let polygoner = super::segment_polygon::SegmentPolygon { + let polygoner = crate::output::viewsheds::segment_polygon::SegmentPolygon { dem_scale, angle, angle_scale, @@ -160,14 +108,17 @@ impl Joiner { /// When a segment doesn't touch anything it becomes its own independent polygon. fn create_new_polygon_from_untouched_segment( - polygon_segment: &super::segment_polygon::Vertices, + polygon_segment: &crate::output::viewsheds::segment_polygon::Vertices, angle: f32, - ) -> Result { + ) -> Result { tracing::debug!( "Segment not touching anything at angle {angle}, \ so making it its own polygon: {polygon_segment:?}" ); - let mut polygon = super::growable_polygon::GrowablePolygon::new(polygon_segment, angle); + let mut polygon = crate::output::viewsheds::growable_polygon::GrowablePolygon::new( + polygon_segment, + angle, + ); if angle == 0.0 { polygon.is_created_at_angle_0 = true; } @@ -195,7 +146,7 @@ impl Joiner { } /// Once all angles have been checked, we can move all active polygons to "completed". - fn move_all_active_polygons_to_completed(&mut self) { + pub(crate) fn move_all_active_polygons_to_completed(&mut self) { let active = self.active.drain(..); self.completed.extend(active); } @@ -244,7 +195,7 @@ impl Joiner { fn join_segment( &mut self, growable_polygon_index: usize, - segment: &super::segment_polygon::Vertices, + segment: &crate::output::viewsheds::segment_polygon::Vertices, maybe_joining_polygon_index: Option, angle: f32, ) -> Result { @@ -262,13 +213,12 @@ impl Joiner { let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); while let Some((index, vertex)) = iterator.next() { match vertex.opening { - super::growable_polygon::Opening::Start(opening_start) => { + Opening::Start(opening_start) => { let Some((index_next, vertex_next)) = iterator.next() else { bail!("Opening without following vertex"); }; - let super::growable_polygon::Opening::End(opening_end) = vertex_next.opening - else { + let Opening::End(opening_end) = vertex_next.opening else { bail!("Opening start not followed by opening end"); }; @@ -278,7 +228,7 @@ impl Joiner { "Checking touch for polygon {growable_polygon_index} at opening index: \ {index}, distances: {opening_range:?}", ); - if super::growable_polygon::GrowablePolygon::is_touching( + if crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( &segment.distances, &opening_range, ) { @@ -296,14 +246,14 @@ impl Joiner { ); } } - super::growable_polygon::Opening::End(_) => { + Opening::End(_) => { bail!("Unexpected opening end reached"); } - super::growable_polygon::Opening::Null - | super::growable_polygon::Opening::GenesisStart(_) - | super::growable_polygon::Opening::GenesisEnd(_) - | super::growable_polygon::Opening::NewStart(_) - | super::growable_polygon::Opening::NewEnd(_) => (), + Opening::Null + | Opening::GenesisStart(_) + | Opening::GenesisEnd(_) + | Opening::NewStart(_) + | Opening::NewEnd(_) => (), } } @@ -342,8 +292,8 @@ impl Joiner { growable_polygon_index: usize, maybe_joining_polygon_index: Option, ) -> ( - &mut super::growable_polygon::GrowablePolygon, - Option<&mut super::growable_polygon::GrowablePolygon>, + &mut crate::output::viewsheds::growable_polygon::GrowablePolygon, + Option<&mut crate::output::viewsheds::growable_polygon::GrowablePolygon>, ) { if let Some(joining_polygon_index) = maybe_joining_polygon_index { #[expect( @@ -369,13 +319,15 @@ impl Joiner { #[cfg(test)] mod test { - use super::*; - #[test] fn multiple_angles() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = Joiner::build(&[segment.clone(), segment.clone(), segment], 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build( + &[segment.clone(), segment.clone(), segment], + 1.0, + ) + .unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -402,7 +354,7 @@ mod test { crate::storage::segments::Segment::new(0, 2), crate::storage::segments::Segment::new(3, 1), ]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ main.clone(), main.clone(), @@ -436,7 +388,7 @@ mod test { fn multiple_polygons() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ segment.clone(), vec![], @@ -471,7 +423,7 @@ mod test { fn multiple_polygons_not_touching() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ segment.clone(), vec![], @@ -507,7 +459,7 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let variance = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[main.clone(), main.clone(), main.clone(), main, variance], 1.0, ) @@ -539,7 +491,11 @@ mod test { crate::storage::segments::Segment::new(3, 1), ]; let bottom = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = Joiner::build(&[main.clone(), pair, bottom, main.clone(), main], 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build( + &[main.clone(), pair, bottom, main.clone(), main], + 1.0, + ) + .unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -563,7 +519,7 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let top = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ main.clone(), top.clone(), @@ -598,8 +554,11 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let lid = vec![crate::storage::segments::Segment::new(2, 1)]; - let joined = - Joiner::build(&[main.clone(), main.clone(), lid, main.clone(), main], 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build( + &[main.clone(), main.clone(), lid, main.clone(), main], + 1.0, + ) + .unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -627,7 +586,7 @@ mod test { crate::storage::segments::Segment::new(2, 1), crate::storage::segments::Segment::new(4, 1), ]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[main.clone(), main.clone(), main.clone(), struts, main], 1.0, ) @@ -658,7 +617,7 @@ mod test { crate::storage::segments::Segment::new(4, 1), ]; let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ polygons.clone(), polygons.clone(), @@ -701,7 +660,9 @@ mod test { let first = vec![higher_but_made_first.clone()]; let second = vec![lower, higher_but_made_first]; let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = Joiner::build(&[vec![], first, second, long, vec![]], 1.0).unwrap(); + let joined = + crate::output::viewsheds::joiner::build(&[vec![], first, second, long, vec![]], 1.0) + .unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -724,7 +685,7 @@ mod test { fn inherit_holes_common() { crate::setup_logging().unwrap(); - let joined = Joiner::build( + let joined = crate::output::viewsheds::joiner::build( &[ vec![], vec![crate::storage::segments::Segment::new(0, 3)], diff --git a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs index bd68e7a..ceb072d 100644 --- a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs +++ b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs @@ -2,7 +2,7 @@ /// The 5 vertices of a segment and their distances from the centre. #[derive(Debug)] -pub struct Vertices { +pub(crate) struct Vertices { /// The 5 vertices of a segment. The 4 corners plus a copy of the first vertex to close the /// polygon. pub vertices: [geo::Coord; 5], @@ -11,7 +11,7 @@ pub struct Vertices { } /// `SegmentPolygon` -pub struct SegmentPolygon { +pub(crate) struct SegmentPolygon { /// Scale of DEM data. pub dem_scale: f32, /// The current sector angle. @@ -22,7 +22,7 @@ pub struct SegmentPolygon { impl SegmentPolygon { /// Make a single polygon representing a visible region of the planet. - pub fn make(&self, opening_index: u32, closing_index: u32) -> Vertices { + pub(crate) fn make(&self, opening_index: u32, closing_index: u32) -> Vertices { let opening_coord = self.index_to_coordinate(opening_index); let closing_coord = self.index_to_coordinate(closing_index); diff --git a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs index 9725f62..1501d66 100644 --- a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs +++ b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs @@ -7,10 +7,10 @@ use color_eyre::eyre::Result; /// but metric projections are not globally correct. So reprojecting to the _viewshed's_ centre /// just gives us that little bit more accuracy, especially for larger DEMs. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Coordinate(pub geo::Coord); +pub(crate) struct Coordinate(pub geo::Coord); /// `Viewshed` -pub struct Viewshed<'viewshed> { +pub(crate) struct Viewshed<'viewshed> { /// The DEM used to compute the final data. pub dem: &'viewshed tvs_lib::dem::DEM, /// Coordinate of the observer for the viewshed we want to reconstruct. @@ -19,7 +19,7 @@ pub struct Viewshed<'viewshed> { impl Viewshed<'_> { /// Reconstruct a viewshed. - pub fn reconstruct( + pub(crate) fn reconstruct( db_path: std::path::PathBuf, requested_lonlat: tvs_lib::projector::LonLatCoord, ) -> Result<(tvs_lib::dem::Coordinate, geo::MultiPolygon)> { @@ -85,7 +85,7 @@ impl Viewshed<'_> { color_eyre::eyre::bail!("Point of view ({:?}) is not calculable", viewshed.pov_coord); } - let multi_polygon = super::joiner_common::Joiner::build(&segments, viewshed.dem.scale)?; + let multi_polygon = crate::output::viewsheds::joiner::build(&segments, viewshed.dem.scale)?; tracing::info!("Viewshed reconstructed in {:?}.", start.elapsed()); Ok((pov_dem_coord, multi_polygon)) @@ -142,7 +142,7 @@ impl Viewshed<'_> { } /// Save the viewshed to disk. - pub fn save( + pub(crate) fn save( viewshed: geo::MultiPolygon, output_directory: &std::path::Path, viewshed_latlon: tvs_lib::projector::LonLatCoord, @@ -163,7 +163,7 @@ impl Viewshed<'_> { #[cfg(test)] impl Viewshed<'_> { /// Convert from the viewshed projection to DEM coordinates. - pub fn convert_viewshed_coord_to_dem_coord( + pub(crate) fn convert_viewshed_coord_to_dem_coord( &self, viewshed_coord: Coordinate, ) -> Result { diff --git a/crates/total-viewsheds/src/post_process.rs b/crates/total-viewsheds/src/post_process.rs index 88fa74f..b59e1b4 100644 --- a/crates/total-viewsheds/src/post_process.rs +++ b/crates/total-viewsheds/src/post_process.rs @@ -4,7 +4,7 @@ use color_eyre::Result; use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _}; /// Run post-processing tasks. -pub fn run(config: &crate::config::PostProcess) -> Result<()> { +pub(crate) fn run(config: &crate::config::PostProcess) -> Result<()> { #[expect( clippy::redundant_closure_for_method_calls, reason = "It's too verbose" diff --git a/crates/total-viewsheds/src/pre_process.rs b/crates/total-viewsheds/src/pre_process.rs index 8ecb543..43401de 100644 --- a/crates/total-viewsheds/src/pre_process.rs +++ b/crates/total-viewsheds/src/pre_process.rs @@ -5,7 +5,9 @@ use std::collections::HashMap; use color_eyre::eyre::{ContextCompat as _, Result}; /// Create a fast lookup for deciding which points should have their viewsheds saved. -pub fn create_biggest_tvs_subgrid(config: &crate::config::Compute) -> Result> { +pub(crate) fn create_biggest_tvs_subgrid( + config: &crate::config::Compute, +) -> Result> { let neighbourhood_size = config .only_save_biggest_viewsheds .context("Must specify --only-save-biggest-viewsheds")?; @@ -20,7 +22,7 @@ pub fn create_biggest_tvs_subgrid(config: &crate::config::Compute) -> Result Result> { @@ -51,7 +53,11 @@ pub fn find_biggest_total_surfaces( } /// Find which neighbourhood a given index is in. -pub const fn get_neighbourhood_id(index: i64, global_width: i64, neighbourhood_size: i64) -> i64 { +pub(crate) const fn get_neighbourhood_id( + index: i64, + global_width: i64, + neighbourhood_size: i64, +) -> i64 { let global_x = index.rem_euclid(global_width); let global_y = index.div_euclid(global_width); diff --git a/crates/total-viewsheds/src/run.rs b/crates/total-viewsheds/src/run.rs index 3b3919d..8a497d6 100644 --- a/crates/total-viewsheds/src/run.rs +++ b/crates/total-viewsheds/src/run.rs @@ -4,7 +4,7 @@ use color_eyre::Result; use std::path::PathBuf; /// Handles all the computations. -pub struct Compute<'compute> +pub(crate) struct Compute<'compute> where 'static: 'compute, { @@ -20,7 +20,7 @@ where #[derive(Clone)] /// Configuration for computing. -pub struct Config { +pub(crate) struct Config { /// The height of the observer that views viewsheds. pub observer_height: f32, /// What to compute. @@ -51,7 +51,7 @@ pub struct Config { impl<'compute> Compute<'compute> { /// Instantiate. - pub fn new(config: Config, dem: &'compute mut tvs_lib::dem::DEM) -> Result { + pub(crate) fn new(config: Config, dem: &'compute mut tvs_lib::dem::DEM) -> Result { if Self::is_process_viewsheds(&config.process) && !cfg!(any(test, feature = "ring_data")) { color_eyre::eyre::bail!( "Viewshed storage is only supported with the ring_data feature, \ @@ -73,13 +73,13 @@ impl<'compute> Compute<'compute> { } /// Are we computing viewsheds? - pub fn is_process_viewsheds(process: &[crate::config::Process]) -> bool { + pub(crate) fn is_process_viewsheds(process: &[crate::config::Process]) -> bool { Self::is_process_everything(process) || process.contains(&crate::config::Process::Viewsheds) } /// Render a heatmap and `.tiff` file of the total surface areas for each point within the computable area of the /// DEM. - pub fn render_total_surfaces(&self) -> Result<()> { + pub(crate) fn render_total_surfaces(&self) -> Result<()> { let Some(output_dir) = &self.config.output_directory else { return Ok(()); }; @@ -107,7 +107,7 @@ impl<'compute> Compute<'compute> { /// Render a heatmap and `.tiff` of the longest lines of sight for each point within the computable area of the /// DEM. - pub fn render_longest_lines(&self) -> Result<()> { + pub(crate) fn render_longest_lines(&self) -> Result<()> { let Some(output_dir) = &self.config.output_directory else { return Ok(()); }; @@ -156,11 +156,11 @@ impl<'compute> Compute<'compute> { } #[cfg(test)] -pub mod test { +pub(crate) mod test { use super::*; use googletest::prelude::*; - pub fn make_dem(elevations: &[i16]) -> tvs_lib::dem::DEM { + pub(crate) fn make_dem(elevations: &[i16]) -> tvs_lib::dem::DEM { let width = elevations.len().isqrt() as u32; let mut dem = tvs_lib::dem::DEM::new( tvs_lib::projector::LonLatCoord((33.33, 33.33).into()), @@ -173,14 +173,14 @@ pub mod test { dem } - pub fn default_metadata() -> tvs_lib::metadata::MetaData { + pub(crate) fn default_metadata() -> tvs_lib::metadata::MetaData { tvs_lib::metadata::MetaData { scale: 1.0, ..Default::default() } } - pub fn big_dem_metadata() -> tvs_lib::metadata::MetaData { + pub(crate) fn big_dem_metadata() -> tvs_lib::metadata::MetaData { tvs_lib::metadata::MetaData { width: 12, max_line_of_sight: 4, @@ -189,7 +189,7 @@ pub mod test { } } - pub fn default_config(temp_db: &tempfile::NamedTempFile) -> Config { + pub(crate) fn default_config(temp_db: &tempfile::NamedTempFile) -> Config { Config { observer_height: 0.8, process: vec![crate::config::Process::Viewsheds], @@ -207,7 +207,7 @@ pub mod test { } } - pub fn compute(dem: &mut tvs_lib::dem::DEM, config: Config) -> Compute<'_> { + pub(crate) fn compute(dem: &mut tvs_lib::dem::DEM, config: Config) -> Compute<'_> { let mut compute = Compute::new(config, dem).unwrap(); compute.run().unwrap(); compute diff --git a/crates/total-viewsheds/src/storage/db.rs b/crates/total-viewsheds/src/storage/db.rs index b440e67..09f7794 100644 --- a/crates/total-viewsheds/src/storage/db.rs +++ b/crates/total-viewsheds/src/storage/db.rs @@ -5,7 +5,7 @@ use std::path::Path; /// How we look up polar segments in the database. #[derive(Debug)] -pub enum ID { +pub(crate) enum ID { /// The precise DEM ID of the polar segment. DEM(i64), /// The neighbourhood ID within which a single DEM ID has been isolated. @@ -13,20 +13,20 @@ pub enum ID { } /// Sqlite DB connection details. -pub struct DB { +pub(crate) struct DB { /// A connection to Sqlite connection: rusqlite::Connection, } impl DB { /// Instantitate. - pub fn new>(db_path: P) -> Result { + pub(crate) fn new>(db_path: P) -> Result { let connection = rusqlite::Connection::open(db_path)?; Ok(Self { connection }) } /// Save the metadata. - pub fn save_metadata(&self, metadata: &tvs_lib::metadata::MetaData) -> Result<()> { + pub(crate) fn save_metadata(&self, metadata: &tvs_lib::metadata::MetaData) -> Result<()> { tracing::debug!("Saving metadata to {:?}...", self.connection.path()); self.connection.execute( @@ -49,7 +49,7 @@ impl DB { } /// Load the metadata. - pub fn load_metadata(&self) -> Result { + pub(crate) fn load_metadata(&self) -> Result { tracing::debug!("Loading metadata from {:?}...", self.connection.path()); let metadata_string: String = @@ -62,7 +62,10 @@ impl DB { } /// Load all the polar segments for a given ID. - pub fn load_segments(&self, id: &ID) -> Result<(Vec>, i64)> { + pub(crate) fn load_segments( + &self, + id: &ID, + ) -> Result<(Vec>, i64)> { tracing::debug!( "Loading polar segments for {id:?} from {:?}...", self.connection.path() diff --git a/crates/total-viewsheds/src/storage/engine.rs b/crates/total-viewsheds/src/storage/engine.rs index 7f35921..5f86335 100644 --- a/crates/total-viewsheds/src/storage/engine.rs +++ b/crates/total-viewsheds/src/storage/engine.rs @@ -6,7 +6,7 @@ use std::sync::mpsc; use std::thread; /// `Engine` is a thread-safe trait to store `PolarSegments` for a given `tvs_id` -pub trait Engine +pub(crate) trait Engine where Self: Send + Sync, { @@ -20,7 +20,7 @@ where } /// `NoopEngine` stores no `PolarSegments`, it exists for testing purposes -pub struct Noop; +pub(crate) struct Noop; impl Engine for Noop { fn store_segments( &self, @@ -48,7 +48,7 @@ impl Engine for Noop { /// /// Because of the large amount of data and need to optimize for quick writes t is not crash safe, /// meaning if your program crashes the underlying sql database will likely be corrupted -pub struct Sqlite { +pub(crate) struct Sqlite { /// `worker_handle` holds the `JoinHandle` for a worker thread which is reading /// from the Receiver end of `sender`'s channel. /// It uses an `Option` so that it can `take` the inner `JoinHandle` inside drop, @@ -60,7 +60,7 @@ pub struct Sqlite { impl Sqlite { /// Create a new `SqliteEngine` storing the database at `path` - pub fn new>(path: P) -> Self { + pub(crate) fn new>(path: P) -> Self { let (tx, rx) = mpsc::sync_channel(1024); // make an owned copy of Path so that it can be moved into the worker diff --git a/crates/total-viewsheds/src/storage/segments.rs b/crates/total-viewsheds/src/storage/segments.rs index 4f13906..ed4a8d8 100644 --- a/crates/total-viewsheds/src/storage/segments.rs +++ b/crates/total-viewsheds/src/storage/segments.rs @@ -4,11 +4,11 @@ /// `Segment` is the rho portion of a line segment in polar coordinates /// as (`rho`: u16, `delta_rho`: u16) which are packed into a single u32 for storage #[derive(Clone, Default)] -pub struct Segment(pub u32); +pub(crate) struct Segment(pub u32); impl Segment { /// `new` creates a `Segment` the segment's start point and the distance - pub fn new(start: u16, distance: u16) -> Self { + pub(crate) fn new(start: u16, distance: u16) -> Self { // pack start/distsance into a u32 in the format of (start|distance) let wide_start: u32 = start.into(); let wide_distance: u32 = distance.into(); @@ -20,7 +20,7 @@ impl Segment { clippy::as_conversions, reason = "the top 16 bits are guaranteed to be 0" )] - pub const fn start(&self) -> u16 { + pub(crate) const fn start(&self) -> u16 { (self.0 >> 16) as u16 } @@ -30,15 +30,11 @@ impl Segment { clippy::cast_possible_truncation, reason = "the top 16 bits are guaranteed to be 0" )] - pub const fn distance(&self) -> u16 { + pub(crate) const fn distance(&self) -> u16 { self.0 as u16 } } -#[expect( - clippy::module_name_repetitions, - reason = "That's the name we use in the DB" -)] /// `PolarSegments` holds the degree of a line of sight and the list /// of visible `Segments` which is constucted through a Run Length /// Encoding algorithm. @@ -47,7 +43,7 @@ impl Segment { /// 1. The kernel is run with higher angular resolutions. /// 2. The rotation maths causes some `dem_id`/angles to have multiple pairs. #[derive(Clone)] -pub struct PolarSegments { +pub(crate) struct PolarSegments { /// `degree` is a whole degree in the range [0, 359] pub degree: u16, /// `visible_segments` is a list of segments visible for a given @@ -66,7 +62,7 @@ impl PolarSegments { clippy::indexing_slicing, reason = "we want to panic if out of indexes are oob" )] - pub fn from_bools(degree: u16, bitmap: &[bool]) -> Self { + pub(crate) fn from_bools(degree: u16, bitmap: &[bool]) -> Self { let mut visible_segments: Vec = Vec::with_capacity(1); let char_slice: &[u8] = bytemuck::cast_slice(bitmap); diff --git a/crates/total-viewsheds/src/storage/worker.rs b/crates/total-viewsheds/src/storage/worker.rs index f529214..05e3b68 100644 --- a/crates/total-viewsheds/src/storage/worker.rs +++ b/crates/total-viewsheds/src/storage/worker.rs @@ -4,7 +4,7 @@ /// A wrapper for a storage `Engine`. It converts bitmaps from the kernel into a `PolarSegments` /// to communicate. Keeping it a concrete struct lets us hide the underlying engine from the user /// but gives us flexibility to NOOP storage for testing purposes -pub struct Worker { +pub(crate) struct Worker { /// `engine` is the underlying storage engine for our `Storage` struct. /// We keep it `Box` so that Storage doesn't have a generic type parameter /// because: @@ -15,7 +15,7 @@ pub struct Worker { impl Worker { /// `new_noop` initializes a Worker with a dummy engine for testing - pub fn new_noop() -> Self { + pub(crate) fn new_noop() -> Self { Self { engine: Box::new(super::engine::Noop), } @@ -23,7 +23,7 @@ impl Worker { /// `new` creates a new Worker with Sqlite as its backing when /// the `test` or `ring_data` feature is enabled, otherwise returning a noop - pub fn new>(path: P) -> Self { + pub(crate) fn new>(path: P) -> Self { if !cfg!(any(test, feature = "ring_data")) { return Self { engine: Box::new(crate::storage::engine::Noop), @@ -36,7 +36,7 @@ impl Worker { } /// `store_bitmap` converts a bitmap into `PolarSegments` and uses its `Engine` to store it - pub fn store_bitmap( + pub(crate) fn store_bitmap( &self, dem_id: u64, neighbourhood_id: Option, @@ -57,7 +57,7 @@ impl Worker { /// /// All segments sent to `recv` will be run in the same transaction to save on transaction /// overhead. This means that any panic or error will end in a corrupted database -pub fn writer>( +pub(crate) fn writer>( path: P, recv: std::sync::mpsc::Receiver<(u64, Option, super::segments::PolarSegments)>, ) -> Result<(), rusqlite::Error> { diff --git a/crates/total-viewsheds/src/tests/fixtures.rs b/crates/total-viewsheds/src/tests/fixtures.rs index 598c13b..e554e62 100644 --- a/crates/total-viewsheds/src/tests/fixtures.rs +++ b/crates/total-viewsheds/src/tests/fixtures.rs @@ -3,7 +3,7 @@ use color_eyre::eyre::Result; /// The nodata value from NASA's SRTM data. -pub const NODATA: f32 = -32768.0; +pub(crate) const NODATA: f32 = -32768.0; // This is a little map to help orient yourself when looking at the results: // @@ -21,7 +21,7 @@ pub const NODATA: f32 = -32768.0; #[inline] #[must_use] /// A DEM tile with a symmetrical single peak. -pub fn single_peak_dem() -> Vec { +pub(crate) fn single_peak_dem() -> Vec { vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, @@ -55,7 +55,7 @@ pub fn single_peak_dem() -> Vec { #[must_use] #[rustfmt::skip] /// A bigger DEM for making viewsheds. -pub fn bigger_dem() -> Vec { +pub(crate) fn bigger_dem() -> Vec { let x = 15; #[expect( clippy::as_conversions, @@ -82,7 +82,7 @@ pub fn bigger_dem() -> Vec { } /// Create the TVS heatmap that a compute run outputs. -pub fn create_tvs_tiff(data: Vec) -> Result { +pub(crate) fn create_tvs_tiff(data: Vec) -> Result { let width = data.len().isqrt(); let driver = gdal::DriverManager::get_driver_by_name("MEM")?; let dataset = driver.create_with_band_type::("", width, width, 1)?; diff --git a/crates/total-viewsheds/src/tile.rs b/crates/total-viewsheds/src/tile.rs index e38ab3d..5826770 100644 --- a/crates/total-viewsheds/src/tile.rs +++ b/crates/total-viewsheds/src/tile.rs @@ -7,7 +7,7 @@ use color_eyre::eyre::{ContextCompat as _, Result}; /// The basic raw data needed to compute total viewsheds. -pub struct Tile { +pub(crate) struct Tile { /// The width of the tile. pub width: u32, /// The size of single elevation sample in metres. @@ -20,7 +20,7 @@ pub struct Tile { impl Tile { /// Load a tile. - pub fn load(config: &crate::config::Compute) -> Result { + pub(crate) fn load(config: &crate::config::Compute) -> Result { if !config.input.exists() { color_eyre::eyre::bail!("Input file not found: {}", config.input.display()); } diff --git a/crates/total-viewsheds/src/workers.rs b/crates/total-viewsheds/src/workers.rs index 7f84de9..8eb452c 100644 --- a/crates/total-viewsheds/src/workers.rs +++ b/crates/total-viewsheds/src/workers.rs @@ -75,7 +75,7 @@ fn compilation_worker( /// create a db at path P only if `is_db_worker`, otherwise return /// a noop worker -pub fn init_worker>( +pub(crate) fn init_worker>( path: P, meta_data: &tvs_lib::metadata::MetaData, is_db_worker: bool, @@ -93,7 +93,7 @@ pub fn init_worker>( impl super::run::Compute<'_> { /// `run_parallel` runs the CPU kernel in parallel #[expect(clippy::expect_used, reason = "We need to panic on failure")] - pub fn run(&mut self) -> Result<()> { + pub(crate) fn run(&mut self) -> Result<()> { let max_los = usize::try_from(self.dem.max_los_as_points)?; let elevations = &self.dem.elevations; From 9174ee73a92ca17af43fdf9fc1c748dd5cbf7eef Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 4 Jul 2026 14:26:42 +0700 Subject: [PATCH 3/7] fix: filter _after_ enumerate for polygon indexes And other review improvemnts --- .../src/output/viewsheds/joiner/last.rs | 80 ++++++++----------- .../src/output/viewsheds/joiner/normal.rs | 47 +++++------ 2 files changed, 56 insertions(+), 71 deletions(-) diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs index 7b17047..abc0a22 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs @@ -23,8 +23,8 @@ impl super::Joiner { let starting_polygon_indexes: Vec = self .completed .iter() - .filter(|item| item.is_created_at_angle_0) .enumerate() + .filter(|item| item.1.is_created_at_angle_0) .map(|item| item.0) .collect(); @@ -63,6 +63,7 @@ impl super::Joiner { .iter() .filter(|item| item.is_created_at_angle_0) .enumerate() + .filter(|item| item.1.is_created_at_angle_0) .map(|item| item.0) .collect(); @@ -161,25 +162,17 @@ impl super::Joiner { let mut maybe_touching_start = None; let mut maybe_touching_end = None; - let (base_polygon, maybe_joining_polygon) = - if let Some(joining_polygon_index) = maybe_joining_polygon_index { - let base_polygon = self - .active - .get_mut(base_polygon_index) - .context("Bad polygon index")?; - let joining_polygon = self - .completed - .get_mut(joining_polygon_index) - .context("Bad polygon index")?; - (base_polygon, Some(joining_polygon)) - } else { - ( - self.active - .get_mut(base_polygon_index) - .context("Bad polygon index")?, - None, - ) - }; + let base_polygon = self + .active + .get_mut(base_polygon_index) + .context("Bad polygon index")?; + + #[expect( + clippy::indexing_slicing, + reason = "A bad index would only be from bad code" + )] + let maybe_joining_polygon = + maybe_joining_polygon_index.map(|index| &mut self.completed[index]); let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); while let Some((index, vertex)) = iterator.next() { @@ -227,35 +220,32 @@ impl super::Joiner { } } - if let Some(touching_start) = maybe_touching_start - && let Some(touching_end) = maybe_touching_end - { - let base_vertices_range = touching_end..touching_start; + let base_vertices_range = match (maybe_touching_start, maybe_touching_end) { + (Some(start), Some(end)) => end..start, + _ => return Ok(false), + }; - match maybe_joining_polygon { - Some(joining_polygon) => { - tracing::trace!("Final angle, polygon BEFORE: {:#?}", base_polygon); - base_polygon.join_starting_polygon( - base_vertices_range, - joining_vertices_range, - joining_polygon, - )?; - tracing::trace!( - "Final angle, polygon AFTER joined polygon: {:#?}", - base_polygon - ); - } - None => { - tracing::trace!("Final angle, self-polygon BEFORE: {:#?}", base_polygon); - base_polygon.join_self(joining_vertices_range, base_vertices_range); - tracing::trace!("Final angle, self-polygon AFTER: {:#?}", base_polygon); - } + match maybe_joining_polygon { + Some(joining_polygon) => { + tracing::trace!("Final angle, polygon BEFORE: {:#?}", base_polygon); + base_polygon.join_starting_polygon( + base_vertices_range, + joining_vertices_range, + joining_polygon, + )?; + tracing::trace!( + "Final angle, polygon AFTER joined polygon: {:#?}", + base_polygon + ); + } + None => { + tracing::trace!("Final angle, self-polygon BEFORE: {:#?}", base_polygon); + base_polygon.join_self(joining_vertices_range, base_vertices_range); + tracing::trace!("Final angle, self-polygon AFTER: {:#?}", base_polygon); } - - return Ok(true); } - Ok(false) + Ok(true) } } diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs index 22b8396..698b8e0 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs @@ -138,10 +138,7 @@ impl super::Joiner { /// If a polygon wasn't touched by a segment from the current angle then it is moved to `self.completed`. fn move_untouched_active_polygons_to_completed(&mut self) { - let completed = self - .active - .extract_if(.., |polygon| !polygon.is_touched) - .collect::>(); + let completed = self.active.extract_if(.., |polygon| !polygon.is_touched); self.completed.extend(completed); } @@ -257,31 +254,29 @@ impl super::Joiner { } } - if let Some(touching_start) = maybe_touching_start - && let Some(touching_end) = maybe_touching_end - { - let base_vertices_range = touching_end..touching_start; - match maybe_joining_polygon { - Some(joining_polygon) => { - base_polygon.join_non_starting_polygon(base_vertices_range, joining_polygon)?; - tracing::trace!( - "Polygon {growable_polygon_index} AFTER joined polygon: {:#?}", - base_polygon - ); - } - None => { - base_polygon.join_segment(segment, base_vertices_range, angle)?; - tracing::trace!( - "Polygon {growable_polygon_index} AFTER joined segment: {:#?}", - base_polygon - ); - } - } + let base_vertices_range = match (maybe_touching_start, maybe_touching_end) { + (Some(start), Some(end)) => end..start, + _ => return Ok(false), + }; - return Ok(true); + match maybe_joining_polygon { + Some(joining_polygon) => { + base_polygon.join_non_starting_polygon(base_vertices_range, joining_polygon)?; + tracing::trace!( + "Polygon {growable_polygon_index} AFTER joined polygon: {:#?}", + base_polygon + ); + } + None => { + base_polygon.join_segment(segment, base_vertices_range, angle)?; + tracing::trace!( + "Polygon {growable_polygon_index} AFTER joined segment: {:#?}", + base_polygon + ); + } } - Ok(false) + Ok(true) } /// Get the polygons involved in a touch check. From dfec26074f2b8639d92894541e8e26458c5758b1 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sun, 5 Jul 2026 16:19:36 +0700 Subject: [PATCH 4/7] refactor: extract viewshed opening scanner And other improvements from review. --- crates/total-viewsheds/src/main.rs | 1 + .../src/output/viewsheds/growable_polygon.rs | 57 ++----- .../src/output/viewsheds/joiner/last.rs | 67 ++------ .../src/output/viewsheds/joiner/normal.rs | 87 +++-------- .../src/output/viewsheds/segment_polygon.rs | 51 ++++--- .../src/output/viewsheds/vertices.rs | 143 ++++++++++++++++++ 6 files changed, 205 insertions(+), 201 deletions(-) create mode 100644 crates/total-viewsheds/src/output/viewsheds/vertices.rs diff --git a/crates/total-viewsheds/src/main.rs b/crates/total-viewsheds/src/main.rs index 8bb6314..af224ba 100644 --- a/crates/total-viewsheds/src/main.rs +++ b/crates/total-viewsheds/src/main.rs @@ -79,6 +79,7 @@ mod output { mod growable_polygon; mod joiner; mod segment_polygon; + mod vertices; pub(crate) mod viewshed; } } diff --git a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs index 1edf30d..39fb8f7 100644 --- a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs +++ b/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs @@ -2,55 +2,15 @@ use color_eyre::eyre::{ContextCompat as _, Result, bail}; -/// An opening is where one polygon can join a segment, or even another polygon. -/// The `u32` values in the variants are for storing the polar distance from the centre. This saves -/// having to do trigonometry to figure out if two openings are touching. -#[derive(Eq, PartialEq, Debug, Clone)] -pub(crate) enum Opening { - /// No opening. We could also just use `Option::None`, but unwrapping it isn't so ergonomic. - Null, - /// This i for polygons that start at 0 degrees. It's possible that another polygon (or even - /// itself), could connect to it at 360 degrees. - GenesisStart(u32), - /// The closing of the genesis opening at 0 degrees. - GenesisEnd(u32), - /// An active opening's start. - Start(u32), - /// An active opening's end. - End(u32), - /// When a segment or polygon is successfully attached to an existing polygon, but we still want - /// to carry on looking for other attachments in the given angle, we don't want to mistakenly - /// attach new segments/polygons to something that's just been attached. Once the whole angle is - /// completed, then these get downgraded to the common `Start/End` opendings. - NewStart(u32), - /// A `New` openings end. - NewEnd(u32), -} - -/// A vertex is a single point in a polygon. -#[derive(Debug, Clone)] -pub(crate) struct Vertex { - /// The coordinate of the vertex in euclidian space. - pub coordinate: geo::Coord, - /// The kind of opening that this vertex represents. - pub opening: Opening, -} - -impl Vertex { - /// Is the vertex at the centre of the viewshed? The centre is a special place, it is a - /// zero-length opening that isn't nulled if no segments/polygons touch it for a given angle. - pub(crate) fn is_centre(&self) -> bool { - self.coordinate.x.abs() == 0.0 && self.coordinate.y.abs() == 0.0 - } -} +use crate::output::viewsheds::vertices::Opening; /// A polygon that grows, but only from its anti-clockwise facing side. #[derive(Debug, Clone)] pub(crate) struct GrowablePolygon { /// The main exterior vertices. - pub vertices: Vec, + pub vertices: Vec, /// Interiore holes within the polygon. - pub holes: Vec>, + pub holes: Vec>, /// The distance of the opening furthest from the centre of the viewshed. Used for ordering /// active polygons. Polygons must be joined to new segments (and other polygons) in increasing /// order to prevent overlaps. @@ -91,7 +51,7 @@ impl GrowablePolygon { 4 => Opening::NewStart(segment_vertices.distances.start), _ => Opening::Null, }; - vertices.push(Vertex { + vertices.push(super::vertices::Vertex { coordinate: *segment_vertex, opening: opening.clone(), }); @@ -184,11 +144,11 @@ impl GrowablePolygon { fn join( &mut self, base_vertices_range: std::ops::Range, - joining_vertices: Vec, + joining_vertices: Vec, ) -> Result<()> { let old_start = self.extract_old_start(base_vertices_range.end)?; - let removed_vertices: Vec = self + let removed_vertices: Vec = self .vertices .splice(base_vertices_range.clone(), joining_vertices) .collect(); @@ -349,7 +309,8 @@ impl GrowablePolygon { "Splicing polygon into itself at: {range:?} ({left_range:?}/{right_range:?})" ); - let removed_vertices: Vec = self.vertices.splice(range, vec![]).collect(); + let removed_vertices: Vec = + self.vertices.splice(range, vec![]).collect(); self.create_holes(&removed_vertices); } @@ -367,7 +328,7 @@ impl GrowablePolygon { } /// Create interior holes from the vertices that were removed for the join. - fn create_holes(&mut self, vertices: &[Vertex]) { + fn create_holes(&mut self, vertices: &[super::vertices::Vertex]) { let holes = vertices.split_inclusive(|vertex| matches!(vertex.opening, Opening::End(_))); for hole in holes { diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs index abc0a22..e890269 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs @@ -5,7 +5,7 @@ use color_eyre::eyre::{ContextCompat as _, Result, bail}; use itertools::Itertools as _; -use crate::output::viewsheds::growable_polygon::Opening; +use crate::output::viewsheds::vertices::Opening; impl super::Joiner { /// The final angle faces the challenge of joining polygons together from the first angle. @@ -41,14 +41,13 @@ impl super::Joiner { for starting_polygon_index in starting_polygon_indexes { for final_polygon_index in 0..self.active.len() { if self - .check_polygons_touching(final_polygon_index, Some(starting_polygon_index))? + .handle_touching_polygons(final_polygon_index, Some(starting_polygon_index))? { touching_starting_polygons.push(starting_polygon_index); } } } - // Reverse order so indices remain valid. let polygons_to_remove = touching_starting_polygons.iter().rev(); tracing::debug!( "Removing final joined polygons: {:?}", @@ -61,14 +60,13 @@ impl super::Joiner { let self_joining_polygon_indexes: Vec = self .active .iter() - .filter(|item| item.is_created_at_angle_0) .enumerate() .filter(|item| item.1.is_created_at_angle_0) .map(|item| item.0) .collect(); for self_joining_polygon_index in self_joining_polygon_indexes { - self.check_polygons_touching(self_joining_polygon_index, None)?; + self.handle_touching_polygons(self_joining_polygon_index, None)?; } tracing::debug!("Final angle done in {:?}", timing.elapsed()); @@ -77,7 +75,7 @@ impl super::Joiner { } /// Check whether a final and a starting polygon are touching. - fn check_polygons_touching( + fn handle_touching_polygons( &mut self, final_polygon_index: usize, maybe_starting_polygon_index: Option, @@ -137,7 +135,7 @@ impl super::Joiner { )?; if is_touching && maybe_starting_polygon_index.is_some() { - self.check_polygons_touching(final_polygon_index, None)?; + self.handle_touching_polygons(final_polygon_index, None)?; return Ok(true); } } @@ -159,8 +157,6 @@ impl super::Joiner { "Checking base polygon {base_polygon_index} \ opening indices: {joining_vertices_range:?}" ); - let mut maybe_touching_start = None; - let mut maybe_touching_end = None; let base_polygon = self .active @@ -174,55 +170,10 @@ impl super::Joiner { let maybe_joining_polygon = maybe_joining_polygon_index.map(|index| &mut self.completed[index]); - let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); - while let Some((index, vertex)) = iterator.next() { - match vertex.opening { - Opening::Start(opening_start) => { - let Some((index_next, vertex_next)) = iterator.next() else { - bail!("Opening without following vertex"); - }; - - let Opening::End(opening_end) = vertex_next.opening else { - bail!("Opening `Start` not followed by opening `End`"); - }; - - let base_opening_range = opening_start..opening_end; - - tracing::trace!("Checking touching for vertex ({:?})...", vertex); - if crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( - &base_opening_range, - &joining_opening_range, - ) { - tracing::trace!( - "Final angle, polygon {base_polygon_index} touches: \ - {base_opening_range:?}/{joining_opening_range:?}" - ); - - if maybe_touching_start.is_none() { - maybe_touching_start = Some(index); - } - maybe_touching_end = Some(index_next + 1); - } else { - tracing::trace!( - "Final angle, polygon {base_polygon_index} does not touch: \ - {base_opening_range:?}/{joining_opening_range:?}" - ); - } - } - Opening::End(_) => { - bail!("Unexpected opening `End` reached"); - } - Opening::Null - | Opening::GenesisStart(_) - | Opening::GenesisEnd(_) - | Opening::NewStart(_) - | Opening::NewEnd(_) => (), - } - } - - let base_vertices_range = match (maybe_touching_start, maybe_touching_end) { - (Some(start), Some(end)) => end..start, - _ => return Ok(false), + let Some(base_vertices_range) = + super::super::vertices::find_contact(&base_polygon.vertices, &joining_opening_range) + else { + return Ok(false); }; match maybe_joining_polygon { diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs index 698b8e0..58acda8 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs @@ -1,10 +1,8 @@ //! Join the visible segments from each computed angle into the viewshed. These "normal" joinings -//! are the ones that occur for every angle other than the final one. Therefore these should simpler -//! and faster. +//! are the ones that occur for every angle other than the final one. Therefore these should be +//! simpler and faster. -use color_eyre::eyre::{Result, bail}; - -use crate::output::viewsheds::growable_polygon::Opening; +use color_eyre::eyre::Result; impl super::Joiner { /// Build the viewshed for a single angle. @@ -28,20 +26,19 @@ impl super::Joiner { let mut new_polygons = Vec::new(); - let polygoner = crate::output::viewsheds::segment_polygon::SegmentPolygon { + let segment = crate::output::viewsheds::segment_polygon::SegmentPolygon { dem_scale, angle, angle_scale, }; let timing = std::time::Instant::now(); - let last_segment_index = segments.len().saturating_sub(1); for (segment_index, polar_segment) in segments.iter().enumerate() { - let is_last_segment = segment_index == last_segment_index; let start = u32::from(polar_segment.start()); let end = u32::from(polar_segment.start() + polar_segment.distance()); tracing::debug!("Segment {segment_index}, distances range: {:?}", start..end); - let polygon_segment = polygoner.make(start, end); + let polygon_segment = + crate::output::viewsheds::segment_polygon::Vertices::new(&segment, start, end); let mut is_segment_touching_anything = false; let mut maybe_joining_polygon_index = None; let mut joined_polygons_to_remove = Vec::new(); @@ -81,14 +78,8 @@ impl super::Joiner { } tracing::debug!("Removing joined polygons: {joined_polygons_to_remove:?}"); - for joined_polygon_index in joined_polygons_to_remove { - self.active.remove(joined_polygon_index); - } - - if is_last_segment { - for polygon in &mut self.active { - polygon.downgrade_openings()?; - } + for joined_polygon_index in joined_polygons_to_remove.iter().rev() { + self.active.remove(*joined_polygon_index); } if !is_segment_touching_anything { @@ -98,6 +89,11 @@ impl super::Joiner { )?); } } + + for polygon in &mut self.active { + polygon.downgrade_openings()?; + } + tracing::debug!("Angle {angle} done in {:?}", timing.elapsed()); self.move_untouched_active_polygons_to_completed(); @@ -196,9 +192,6 @@ impl super::Joiner { maybe_joining_polygon_index: Option, angle: f32, ) -> Result { - let mut maybe_touching_start = None; - let mut maybe_touching_end = None; - let (base_polygon, maybe_joining_polygon) = self.get_involved_polygons(growable_polygon_index, maybe_joining_polygon_index); @@ -207,56 +200,10 @@ impl super::Joiner { base_polygon ); - let mut iterator = base_polygon.vertices.iter_mut().enumerate().rev(); - while let Some((index, vertex)) = iterator.next() { - match vertex.opening { - Opening::Start(opening_start) => { - let Some((index_next, vertex_next)) = iterator.next() else { - bail!("Opening without following vertex"); - }; - - let Opening::End(opening_end) = vertex_next.opening else { - bail!("Opening start not followed by opening end"); - }; - - let opening_range = opening_start..opening_end; - - tracing::debug!( - "Checking touch for polygon {growable_polygon_index} at opening index: \ - {index}, distances: {opening_range:?}", - ); - if crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( - &segment.distances, - &opening_range, - ) { - tracing::debug!( - "🟢 Polygon {growable_polygon_index} touches segment opening" - ); - - if maybe_touching_start.is_none() { - maybe_touching_start = Some(index); - } - maybe_touching_end = Some(index_next + 1); - } else { - tracing::debug!( - "🟡 Polygon {growable_polygon_index} does not touch segment opening" - ); - } - } - Opening::End(_) => { - bail!("Unexpected opening end reached"); - } - Opening::Null - | Opening::GenesisStart(_) - | Opening::GenesisEnd(_) - | Opening::NewStart(_) - | Opening::NewEnd(_) => (), - } - } - - let base_vertices_range = match (maybe_touching_start, maybe_touching_end) { - (Some(start), Some(end)) => end..start, - _ => return Ok(false), + let Some(base_vertices_range) = + super::super::vertices::find_contact(&base_polygon.vertices, &segment.distances) + else { + return Ok(false); }; match maybe_joining_polygon { diff --git a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs index ceb072d..5b64082 100644 --- a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs +++ b/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs @@ -10,29 +10,19 @@ pub(crate) struct Vertices { pub distances: std::ops::Range, } -/// `SegmentPolygon` -pub(crate) struct SegmentPolygon { - /// Scale of DEM data. - pub dem_scale: f32, - /// The current sector angle. - pub angle: f32, - /// The number of angles per degree. - pub angle_scale: f32, -} - -impl SegmentPolygon { +impl Vertices { /// Make a single polygon representing a visible region of the planet. - pub(crate) fn make(&self, opening_index: u32, closing_index: u32) -> Vertices { - let opening_coord = self.index_to_coordinate(opening_index); - let closing_coord = self.index_to_coordinate(closing_index); + pub(crate) fn new(segment: &SegmentPolygon, opening_index: u32, closing_index: u32) -> Self { + let opening_coord = segment.index_to_coordinate(opening_index); + let closing_coord = segment.index_to_coordinate(closing_index); - let spread = 1.0f64 / f64::from(self.angle_scale) / 2.0f64; - let bottom_left = Self::rotate_by(opening_coord, spread); - let bottom_right = Self::rotate_by(opening_coord, -spread); - let top_left = Self::rotate_by(closing_coord, spread); - let top_right = Self::rotate_by(closing_coord, -spread); + let spread = 1.0f64 / f64::from(segment.angle_scale) / 2.0f64; + let bottom_left = SegmentPolygon::rotate_by(opening_coord, spread); + let bottom_right = SegmentPolygon::rotate_by(opening_coord, -spread); + let top_left = SegmentPolygon::rotate_by(closing_coord, spread); + let top_right = SegmentPolygon::rotate_by(closing_coord, -spread); - let scale = f64::from(self.dem_scale); + let scale = f64::from(segment.dem_scale); let vertices = [ bottom_left * scale, @@ -42,12 +32,24 @@ impl SegmentPolygon { bottom_left * scale, ]; - Vertices { + Self { vertices, distances: opening_index..closing_index, } } +} +/// `SegmentPolygon` +pub(crate) struct SegmentPolygon { + /// Scale of DEM data. + pub dem_scale: f32, + /// The current sector angle. + pub angle: f32, + /// The number of angles per degree. + pub angle_scale: f32, +} + +impl SegmentPolygon { /// Convert an index along a line of sight into a coordinate. fn index_to_coordinate(&self, index: u32) -> super::viewshed::Coordinate { let radians = self.angle.to_radians(); @@ -103,10 +105,9 @@ mod test { dem: &dem, pov_coord: tvs_lib::dem::Coordinate(setup.pov), }; - let viewsheder = builder(&viewshed, setup.angle); - let vertices = viewsheder - .make(setup.opening_index, setup.closing_index) - .vertices; + let segment = builder(&viewshed, setup.angle); + let vertices = + super::Vertices::new(&segment, setup.opening_index, setup.closing_index).vertices; let polygon = geo::Polygon::new(geo::LineString(vertices.into()), vec![]); let mut polygon_as_dem_coords = Vec::new(); diff --git a/crates/total-viewsheds/src/output/viewsheds/vertices.rs b/crates/total-viewsheds/src/output/viewsheds/vertices.rs new file mode 100644 index 0000000..a6f8c66 --- /dev/null +++ b/crates/total-viewsheds/src/output/viewsheds/vertices.rs @@ -0,0 +1,143 @@ +//! The vertices of a viewshed polygon. They have both real-world coordinates and "openings", which +//! are vertices that can join with other "openings". + +/// A vertex is a single point in a polygon. +#[derive(Debug, Clone)] +pub(crate) struct Vertex { + /// The coordinate of the vertex in euclidian space. + pub coordinate: geo::Coord, + /// The kind of opening that this vertex represents. + pub opening: Opening, +} + +impl Vertex { + /// Is the vertex at the centre of the viewshed? The centre is a special place, it is a + /// zero-length opening that isn't nulled if no segments/polygons touch it for a given angle. + pub(crate) fn is_centre(&self) -> bool { + self.coordinate.x.abs() == 0.0 && self.coordinate.y.abs() == 0.0 + } +} + +/// An opening is where one polygon can join a segment, or even another polygon. +/// The `u32` values in the variants are for storing the polar distance from the centre. This saves +/// having to do trigonometry to figure out if two openings are touching. +#[derive(Eq, PartialEq, Debug, Clone)] +pub(crate) enum Opening { + /// No opening. We could also just use `Option::None`, but unwrapping it isn't so ergonomic. + Null, + /// This i for polygons that start at 0 degrees. It's possible that another polygon (or even + /// itself), could connect to it at 360 degrees. + GenesisStart(u32), + /// The closing of the genesis opening at 0 degrees. + GenesisEnd(u32), + /// An active opening's start. + Start(u32), + /// An active opening's end. + End(u32), + /// When a segment or polygon is successfully attached to an existing polygon, but we still want + /// to carry on looking for other attachments in the given angle, we don't want to mistakenly + /// attach new segments/polygons to something that's just been attached. Once the whole angle is + /// completed, then these get downgraded to the common `Start/End` opendings. + NewStart(u32), + /// A `New` openings end. + NewEnd(u32), +} + +/// Iterator for finding start/open vertex pairs for openings. +pub(crate) struct OpeningsIterator<'vertices> { + /// The vertices to scan. + vertices: &'vertices [Vertex], + /// The current index of the iterator. + index: usize, + /// Keep track of the previously scanned opening. + previous_opening: Opening, +} + +impl<'vertices> OpeningsIterator<'vertices> { + /// Instantiate. + pub(crate) const fn new(vertices: &'vertices [Vertex]) -> Self { + Self { + vertices, + index: vertices.len() - 1, + previous_opening: Opening::Null, + } + } +} + +impl Iterator for OpeningsIterator<'_> { + type Item = (usize, std::ops::Range); + + fn next(&mut self) -> Option { + loop { + if self.index <= 1 { + return None; + } + + #[expect(clippy::indexing_slicing, reason = "Bounds are enforced elsewhere")] + let (left, right) = { (&self.vertices[self.index], &self.vertices[self.index - 1]) }; + + let is_currently_end = matches!(left.opening, Opening::End(_)); + let is_previously_start = matches!(self.previous_opening, Opening::Start(_)); + let is_end_without_a_start = is_currently_end && !is_previously_start; + assert!(!is_end_without_a_start, "Dangling Opening::End"); + if let Opening::Start(start) = left.opening { + #[expect( + clippy::panic, + reason = "Opening ordering is a requirement of the algorithm" + )] + let Opening::End(end) = right.opening else { + panic!("Opening::Start not followed by Opening::End"); + }; + + self.previous_opening = right.opening.clone(); + let opening_range = start..end; + let opening_index = self.index; + self.index -= 2; + return Some((opening_index, opening_range)); + } + + self.previous_opening = left.opening.clone(); + self.index -= 1; + } + } +} + +/// Check if 2 openings touch. +pub(crate) fn find_contact( + vertices: &[Vertex], + joining_opening_range: &std::ops::Range, +) -> Option> { + let mut maybe_touching_start = None; + let mut maybe_touching_end = None; + + let openings = OpeningsIterator::new(vertices); + + for (base_opening_index, base_opening_range) in openings { + tracing::debug!( + "Checking if openings touch: \ + index: {base_opening_index:?}, \ + base distances: {base_opening_range:?}, \ + joining distances: {joining_opening_range:?}", + ); + + if !crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( + &base_opening_range, + joining_opening_range, + ) { + tracing::debug!("🟡 Openings don't touch"); + continue; + } + + tracing::debug!("🟢 Openings touch"); + + if maybe_touching_start.is_none() { + maybe_touching_start = Some(base_opening_index); + } + maybe_touching_end = Some(base_opening_index); + } + + match (maybe_touching_start, maybe_touching_end) { + (Some(start), Some(end)) => Some(end..start), + _ => None, + } +} From f299cd15e47706876fadac392148556594ab10af Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Mon, 6 Jul 2026 17:47:32 +0700 Subject: [PATCH 5/7] tmp. merge with previous commit --- .../src/output/viewsheds/joiner.rs | 65 ++++---- .../src/output/viewsheds/joiner/last.rs | 14 +- .../src/output/viewsheds/joiner/normal.rs | 30 ++-- .../src/output/viewsheds/vertices.rs | 147 ++++++++++++++---- .../src/output/viewsheds/viewshed.rs | 5 +- 5 files changed, 179 insertions(+), 82 deletions(-) diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner.rs b/crates/total-viewsheds/src/output/viewsheds/joiner.rs index 38abecb..1052a7b 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner.rs @@ -10,46 +10,57 @@ use color_eyre::Result; struct Joiner { /// Polygons that don't intersect with the current angle. They don't need to be checked against /// new segments. - completed: Vec, + completed: Vec, /// Polygons that intersect with the current angle. They must be checked to see if any of their /// openings touch any of the segments of the current angle. - active: Vec, + active: Vec, } -/// Build a viewshed of euclidean polygons from polar segments. -pub(crate) fn build( - data: &[Vec], - dem_scale: f32, -) -> Result { - #[expect( - clippy::as_conversions, - clippy::cast_precision_loss, - reason = "The angle count should never strain the f32 mantissa" - )] - let angle_count = data.len() as f32; - let angle_scale = angle_count / 360.0; - - let mut joiner = Joiner { - completed: Vec::new(), - active: Vec::new(), - }; - - for (anglish, segments) in data.iter().enumerate() { +impl Joiner { + /// Join segments into a collection of polygons. + fn join( + data: &[Vec], + dem_scale: f32, + ) -> Result> { #[expect( clippy::as_conversions, clippy::cast_precision_loss, reason = "The angle count should never strain the f32 mantissa" )] - let angle = anglish as f32 / angle_scale; + let angle_count = data.len() as f32; + let angle_scale = angle_count / 360.0; - joiner.build_angle(angle, angle_scale, segments, dem_scale)?; - } + let mut joiner = Self { + completed: Vec::new(), + active: Vec::new(), + }; + + for (anglish, segments) in data.iter().enumerate() { + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "The angle count should never strain the f32 mantissa" + )] + let angle = anglish as f32 / angle_scale; + + joiner.build_angle(angle, angle_scale, segments, dem_scale)?; + } - joiner.build_final_angle()?; - joiner.move_all_active_polygons_to_completed(); + joiner.build_final_angle()?; + joiner.move_all_active_polygons_to_completed(); + Ok(joiner.completed) + } +} + +/// Build a viewshed of euclidean polygons from polar segments. +pub(crate) fn build_viewshed_polygon( + data: &[Vec], + dem_scale: f32, +) -> Result { + let raw_polygons = Joiner::join(data, dem_scale)?; let mut geo_polygons = Vec::new(); - for mut raw_polygon in joiner.completed { + for mut raw_polygon in raw_polygons { raw_polygon.dedup_vertices_ignore_openings(); let polygon = raw_polygon.to_geo_polygon(); tracing::trace!("Final viewshed, adding polygon: {polygon:?}"); diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs index e890269..236efea 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs @@ -206,7 +206,7 @@ mod test { fn final_segment_joins_starting_segment_simple() { crate::setup_logging().unwrap(); let part = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[part.clone(), vec![], vec![], vec![], vec![], part], 1.0, ) @@ -232,7 +232,7 @@ mod test { #[test] fn inherit_holes_final() { crate::setup_logging().unwrap(); - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ vec![crate::storage::segments::Segment::new(0, 3)], vec![ @@ -273,7 +273,7 @@ mod test { crate::storage::segments::Segment::new(0, 1), crate::storage::segments::Segment::new(2, 1), ]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[starting, vec![], vec![], vec![], vec![], long], 1.0, ) @@ -304,7 +304,7 @@ mod test { crate::storage::segments::Segment::new(2, 1), crate::storage::segments::Segment::new(4, 1), ]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[starting, long.clone(), vec![], vec![], vec![], long], 1.0, ) @@ -332,7 +332,7 @@ mod test { crate::setup_logging().unwrap(); let long = vec![crate::storage::segments::Segment::new(0, 4)]; let short = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ short.clone(), long.clone(), @@ -369,7 +369,7 @@ mod test { crate::setup_logging().unwrap(); let part = vec![crate::storage::segments::Segment::new(2, 2)]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner::build(&ring, 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon(&ring, 1.0).unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -396,7 +396,7 @@ mod test { crate::storage::segments::Segment::new(3, 1), ]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner::build(&ring, 1.0).unwrap(); + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon(&ring, 1.0).unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs index 58acda8..0a7e9eb 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs +++ b/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs @@ -265,7 +265,7 @@ mod test { fn multiple_angles() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[segment.clone(), segment.clone(), segment], 1.0, ) @@ -296,7 +296,7 @@ mod test { crate::storage::segments::Segment::new(0, 2), crate::storage::segments::Segment::new(3, 1), ]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ main.clone(), main.clone(), @@ -330,7 +330,7 @@ mod test { fn multiple_polygons() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ segment.clone(), vec![], @@ -365,7 +365,7 @@ mod test { fn multiple_polygons_not_touching() { crate::setup_logging().unwrap(); let segment = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ segment.clone(), vec![], @@ -401,7 +401,7 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let variance = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[main.clone(), main.clone(), main.clone(), main, variance], 1.0, ) @@ -433,7 +433,7 @@ mod test { crate::storage::segments::Segment::new(3, 1), ]; let bottom = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[main.clone(), pair, bottom, main.clone(), main], 1.0, ) @@ -461,7 +461,7 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let top = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ main.clone(), top.clone(), @@ -496,7 +496,7 @@ mod test { crate::setup_logging().unwrap(); let main = vec![crate::storage::segments::Segment::new(0, 4)]; let lid = vec![crate::storage::segments::Segment::new(2, 1)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[main.clone(), main.clone(), lid, main.clone(), main], 1.0, ) @@ -528,7 +528,7 @@ mod test { crate::storage::segments::Segment::new(2, 1), crate::storage::segments::Segment::new(4, 1), ]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[main.clone(), main.clone(), main.clone(), struts, main], 1.0, ) @@ -559,7 +559,7 @@ mod test { crate::storage::segments::Segment::new(4, 1), ]; let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ polygons.clone(), polygons.clone(), @@ -602,9 +602,11 @@ mod test { let first = vec![higher_but_made_first.clone()]; let second = vec![lower, higher_but_made_first]; let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = - crate::output::viewsheds::joiner::build(&[vec![], first, second, long, vec![]], 1.0) - .unwrap(); + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + &[vec![], first, second, long, vec![]], + 1.0, + ) + .unwrap(); let actual = crate::output::ascii::rasterise(joined); let expected = [ "████████████████████████", @@ -627,7 +629,7 @@ mod test { fn inherit_holes_common() { crate::setup_logging().unwrap(); - let joined = crate::output::viewsheds::joiner::build( + let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( &[ vec![], vec![crate::storage::segments::Segment::new(0, 3)], diff --git a/crates/total-viewsheds/src/output/viewsheds/vertices.rs b/crates/total-viewsheds/src/output/viewsheds/vertices.rs index a6f8c66..d212c98 100644 --- a/crates/total-viewsheds/src/output/viewsheds/vertices.rs +++ b/crates/total-viewsheds/src/output/viewsheds/vertices.rs @@ -67,9 +67,20 @@ impl<'vertices> OpeningsIterator<'vertices> { impl Iterator for OpeningsIterator<'_> { type Item = (usize, std::ops::Range); + #[expect( + clippy::panic, + reason = "Opening ordering is a requirement of the algorithm" + )] fn next(&mut self) -> Option { loop { - if self.index <= 1 { + if self.index == 0 { + #[expect( + clippy::indexing_slicing, + reason = "There always has to be a first element" + )] + if matches!(self.vertices[0].opening, Opening::Start(_)) { + panic!("Dangling Opening::Start"); + }; return None; } @@ -80,11 +91,8 @@ impl Iterator for OpeningsIterator<'_> { let is_previously_start = matches!(self.previous_opening, Opening::Start(_)); let is_end_without_a_start = is_currently_end && !is_previously_start; assert!(!is_end_without_a_start, "Dangling Opening::End"); + if let Opening::Start(start) = left.opening { - #[expect( - clippy::panic, - reason = "Opening ordering is a requirement of the algorithm" - )] let Opening::End(end) = right.opening else { panic!("Opening::Start not followed by Opening::End"); }; @@ -92,7 +100,7 @@ impl Iterator for OpeningsIterator<'_> { self.previous_opening = right.opening.clone(); let opening_range = start..end; let opening_index = self.index; - self.index -= 2; + self.index = self.index.saturating_sub(2); return Some((opening_index, opening_range)); } @@ -107,37 +115,110 @@ pub(crate) fn find_contact( vertices: &[Vertex], joining_opening_range: &std::ops::Range, ) -> Option> { - let mut maybe_touching_start = None; - let mut maybe_touching_end = None; - - let openings = OpeningsIterator::new(vertices); - - for (base_opening_index, base_opening_range) in openings { - tracing::debug!( - "Checking if openings touch: \ - index: {base_opening_index:?}, \ - base distances: {base_opening_range:?}, \ - joining distances: {joining_opening_range:?}", - ); - - if !crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( - &base_opening_range, - joining_opening_range, - ) { - tracing::debug!("🟡 Openings don't touch"); - continue; - } + OpeningsIterator::new(vertices) + .filter(|(base_opening_index, base_opening_range)| { + tracing::debug!( + "Checking if openings touch: \ + index: {base_opening_index:?}, \ + base distances: {base_opening_range:?}, \ + joining distances: {joining_opening_range:?}", + ); + + let is_touching = + crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( + base_opening_range, + joining_opening_range, + ); + + if is_touching { + tracing::debug!("🟢 Openings touch"); + } else { + tracing::debug!("🟡 Openings don't touch"); + } + + is_touching + }) + .fold(None, |accumulator, (index, _)| { + let range_of_touch = accumulator.map_or(index..index, |range| { + std::cmp::min(range.start, index)..std::cmp::max(range.end, index) + }); + + Some(range_of_touch) + }) +} - tracing::debug!("🟢 Openings touch"); +#[cfg(test)] +mod test { + use super::*; - if maybe_touching_start.is_none() { - maybe_touching_start = Some(base_opening_index); + fn vertex(opening: super::Opening) -> Vertex { + super::Vertex { + coordinate: geo::Coord::zero(), + opening, } - maybe_touching_end = Some(base_opening_index); } - match (maybe_touching_start, maybe_touching_end) { - (Some(start), Some(end)) => Some(end..start), - _ => None, + fn run(mut vertices: Vec) -> Vec<(usize, std::ops::Range)> { + vertices.reverse(); + OpeningsIterator::new(&vertices).collect::>() + } + + // TODO: + // I actually think side by side openings would themselves be a bug, but I haven't + // verified that so better test for it just in case. + #[test] + fn openings_side_by_side_should_not_panic() { + let vertices = vec![ + vertex(super::Opening::Start(0)), + vertex(super::Opening::End(1)), + vertex(super::Opening::Start(3)), + vertex(super::Opening::End(4)), + ]; + assert_eq!(run(vertices), vec![(3, 0..1), (1, 3..4)]); + } + + #[test] + #[should_panic(expected = "Opening::Start not followed by Opening::End")] + fn start_not_followed_by_end_panics() { + let vertices = vec![ + vertex(super::Opening::Start(0)), + vertex(super::Opening::Null), + vertex(super::Opening::End(0)), + ]; + run(vertices); + } + + #[test] + #[should_panic(expected = "Dangling Opening::Start")] + fn start_in_final_position_panics() { + let vertices = vec![ + vertex(super::Opening::Null), + vertex(super::Opening::Null), + vertex(super::Opening::Start(0)), + ]; + run(vertices); + } + + #[test] + #[should_panic(expected = "Dangling Opening::End")] + fn end_in_initial_position_panics() { + let vertices = vec![ + vertex(super::Opening::End(0)), + vertex(super::Opening::Start(0)), + vertex(super::Opening::End(0)), + ]; + run(vertices); + } + + #[test] + #[should_panic(expected = "Dangling Opening::End")] + fn dangling_end_panics() { + let vertices = vec![ + vertex(super::Opening::Null), + vertex(super::Opening::End(0)), + vertex(super::Opening::Start(0)), + vertex(super::Opening::End(0)), + ]; + run(vertices); } } diff --git a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs index 1501d66..9c38da3 100644 --- a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs +++ b/crates/total-viewsheds/src/output/viewsheds/viewshed.rs @@ -85,7 +85,10 @@ impl Viewshed<'_> { color_eyre::eyre::bail!("Point of view ({:?}) is not calculable", viewshed.pov_coord); } - let multi_polygon = crate::output::viewsheds::joiner::build(&segments, viewshed.dem.scale)?; + let multi_polygon = crate::output::viewsheds::joiner::build_viewshed_polygon( + &segments, + viewshed.dem.scale, + )?; tracing::info!("Viewshed reconstructed in {:?}.", start.elapsed()); Ok((pov_dem_coord, multi_polygon)) From 0dc125d7ce0c9f02db81c6b2334979bfeca12701 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Wed, 8 Jul 2026 14:59:27 +0700 Subject: [PATCH 6/7] refactor: move viewshed reconstruction into crate This prepares for compiling this code to WASM. --- Cargo.lock | 12 ++ Cargo.toml | 3 + crates/total-viewsheds/Cargo.toml | 5 +- crates/total-viewsheds/src/main.rs | 15 +- crates/total-viewsheds/src/output/ascii.rs | 179 +-------------- .../src/output/{viewsheds => }/viewshed.rs | 55 ++++- crates/total-viewsheds/src/storage/db.rs | 8 +- .../total-viewsheds/src/storage/segments.rs | 41 +--- crates/total-viewsheds/src/tests/fixtures.rs | 30 --- crates/tvs-lib/src/ascii.rs | 137 ++++++++++++ .../src/output => tvs-lib/src}/bresenham.rs | 4 +- crates/tvs-lib/src/lib.rs | 2 + crates/viewshed-reconstructor/Cargo.toml | 20 ++ crates/viewshed-reconstructor/README.md | 5 + .../src}/growable_polygon.rs | 41 ++-- .../src}/joiner.rs | 61 ++++-- .../src}/joiner/last.rs | 118 +++++----- .../src}/joiner/normal.rs | 203 ++++++++---------- crates/viewshed-reconstructor/src/lib.rs | 28 +++ crates/viewshed-reconstructor/src/polygon.rs | 39 ++++ crates/viewshed-reconstructor/src/segment.rs | 43 ++++ .../src}/segment_polygon.rs | 117 +++++----- .../src}/vertices.rs | 17 +- 23 files changed, 642 insertions(+), 541 deletions(-) rename crates/total-viewsheds/src/output/{viewsheds => }/viewshed.rs (89%) create mode 100644 crates/tvs-lib/src/ascii.rs rename crates/{total-viewsheds/src/output => tvs-lib/src}/bresenham.rs (96%) create mode 100644 crates/viewshed-reconstructor/Cargo.toml create mode 100644 crates/viewshed-reconstructor/README.md rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/growable_polygon.rs (94%) rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/joiner.rs (53%) rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/joiner/last.rs (81%) rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/joiner/normal.rs (79%) create mode 100644 crates/viewshed-reconstructor/src/lib.rs create mode 100644 crates/viewshed-reconstructor/src/polygon.rs create mode 100644 crates/viewshed-reconstructor/src/segment.rs rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/segment_polygon.rs (62%) rename crates/{total-viewsheds/src/output/viewsheds => viewshed-reconstructor/src}/vertices.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index d0dc4a3..21d5772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,6 +1364,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tvs-lib", + "viewshed-reconstructor", ] [[package]] @@ -1484,6 +1485,17 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "viewshed-reconstructor" +version = "0.1.0" +dependencies = [ + "color-eyre", + "geo", + "tracing", + "tracing-subscriber", + "tvs-lib", +] + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 0ce23b3..1465e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/total-viewsheds", "crates/tvs-lib", + "crates/viewshed-reconstructor", ] [workspace.dependencies] @@ -10,6 +11,8 @@ color-eyre = "0.6.5" geo = "0.31.0" serde = "1.0.219" serde_json = "1.0.143" +tracing = { version = "0.1.41" } +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } # Canonical lints for whole crate # diff --git a/crates/total-viewsheds/Cargo.toml b/crates/total-viewsheds/Cargo.toml index a3b8c35..28bd1f8 100644 --- a/crates/total-viewsheds/Cargo.toml +++ b/crates/total-viewsheds/Cargo.toml @@ -18,9 +18,10 @@ memchr = "2.8.0" serde_json.workspace = true rusqlite = { version = "0.38.0", features = ["bundled"] } rayon = "1.12.0" -tracing = { version = "0.1.41" } -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tracing.workspace = true +tracing-subscriber.workspace = true tvs-lib = { path = "../tvs-lib", version = "0.1.0" } +viewshed-reconstructor = { path = "../viewshed-reconstructor", version = "0.1.0" } [dev-dependencies] tempfile = "3.20.0" diff --git a/crates/total-viewsheds/src/main.rs b/crates/total-viewsheds/src/main.rs index af224ba..8f281b4 100644 --- a/crates/total-viewsheds/src/main.rs +++ b/crates/total-viewsheds/src/main.rs @@ -70,18 +70,9 @@ mod storage { /// Various ways to output data. mod output { mod ascii; - mod bresenham; pub(crate) mod png; pub(crate) mod tiff; - - /// Load, parse and reconstruct euclidean polygon viewsheds from their raw polar segments. - pub(crate) mod viewsheds { - mod growable_polygon; - mod joiner; - mod segment_polygon; - mod vertices; - pub(crate) mod viewshed; - } + pub(crate) mod viewshed; } fn main() -> Result<()> { @@ -98,11 +89,11 @@ fn main() -> Result<()> { geo::coord! {x: f64::from(coordinate.0), y: f64::from(coordinate.1)}, ); - let (_, viewshed) = crate::output::viewsheds::viewshed::Viewshed::reconstruct( + let (_, viewshed) = crate::output::viewshed::Viewshed::reconstruct( viewshed_config.db_path.clone(), geo_coord, )?; - crate::output::viewsheds::viewshed::Viewshed::save( + crate::output::viewshed::Viewshed::save( viewshed, &viewshed_config.output_dir, geo_coord, diff --git a/crates/total-viewsheds/src/output/ascii.rs b/crates/total-viewsheds/src/output/ascii.rs index d451f08..4f5075b 100644 --- a/crates/total-viewsheds/src/output/ascii.rs +++ b/crates/total-viewsheds/src/output/ascii.rs @@ -1,12 +1,6 @@ -//! Output viewsheds as ASCII. Most likely useful for tests. - -#![cfg(test)] -#![expect(clippy::indexing_slicing, reason = "This code is mostly for tests")] - -use geo::CoordsIter as _; - -use crate::output::viewsheds::viewshed::Viewshed; +//! Convert a viewshed to pure text. For testing. +#[cfg(test)] pub(crate) fn make_viewshed( elevations: &[i16], viewshed_pov: geo::Coord, @@ -24,8 +18,8 @@ pub(crate) fn make_viewshed( crate::run::test::compute(&mut dem, config.clone()); let (pov_coord, mut viewshed) = - Viewshed::reconstruct(config.viewsheds_db_path, coord_lonlat).unwrap(); - let viewsheder = crate::output::viewsheds::viewshed::Viewshed { + super::viewshed::Viewshed::reconstruct(config.viewsheds_db_path, coord_lonlat).unwrap(); + let viewsheder = crate::output::viewshed::Viewshed { dem: &dem, pov_coord, }; @@ -34,9 +28,9 @@ pub(crate) fn make_viewshed( polygons.exterior_mut(|line| { for coordinate in line.coords_mut() { *coordinate = viewsheder - .convert_viewshed_coord_to_dem_coord( - crate::output::viewsheds::viewshed::Coordinate(*coordinate), - ) + .convert_viewshed_coord_to_dem_coord(crate::output::viewshed::Coordinate( + *coordinate, + )) .unwrap(); } }); @@ -45,165 +39,14 @@ pub(crate) fn make_viewshed( for interiror_line in interior { for coordinate in interiror_line.coords_mut() { *coordinate = viewsheder - .convert_viewshed_coord_to_dem_coord( - crate::output::viewsheds::viewshed::Coordinate(*coordinate), - ) + .convert_viewshed_coord_to_dem_coord(crate::output::viewshed::Coordinate( + *coordinate, + )) .unwrap(); } } }); } - rasterise_multi_polygon(&viewshed, dem.width) -} - -pub(crate) fn rasterise_multi_polygon( - multi_polygon: &geo::MultiPolygon, - width: u32, -) -> Vec { - let scale = std::env::var("ASCII_TEST_SIZE") - .unwrap_or_else(|_| "2".to_owned()) - .parse::() - .expect("ASCII_TEST_SIZE env not a valid integer"); - let scale_f64 = f64::from(scale); - let raster_width = usize::try_from(width * scale).unwrap(); - - let mut raster: Vec> = Vec::new(); - for _ in 0..width * scale { - let raster_line = vec![false; raster_width]; - raster.push(raster_line); - } - - for polygon in multi_polygon.iter() { - let mut maybe_exterior_start = None; - for coordinate in polygon.exterior_coords_iter() { - if maybe_exterior_start.is_none() { - maybe_exterior_start = Some(coordinate); - continue; - } - - let from = maybe_exterior_start.unwrap(); - #[expect( - clippy::as_conversions, - clippy::cast_possible_truncation, - reason = "Gotta rasterise" - )] - let rasteriser = crate::output::bresenham::Bresenham::new( - super::bresenham::RasterCoord { - x: (from.x * scale_f64).round() as i32, - y: (from.y * scale_f64).round() as i32, - }, - super::bresenham::RasterCoord { - x: (coordinate.x * scale_f64).round() as i32, - y: (coordinate.y * scale_f64).round() as i32, - }, - ); - for coord in rasteriser { - if coord.x >= 0i32 && coord.y >= 0i32 { - let x = usize::try_from(coord.x).unwrap(); - let y = usize::try_from(coord.y).unwrap(); - raster[y][x] = true; - } - } - maybe_exterior_start = Some(coordinate); - } - - for interiors in polygon.interiors() { - let mut maybe_interior_start = None; - for coordinate in interiors { - if maybe_interior_start.is_none() { - maybe_interior_start = Some(*coordinate); - continue; - } - - let from = maybe_interior_start.unwrap(); - #[expect( - clippy::as_conversions, - clippy::cast_possible_truncation, - reason = "Gotta rasterise" - )] - let rasteriser = crate::output::bresenham::Bresenham::new( - super::bresenham::RasterCoord { - x: (from.x * scale_f64).round() as i32, - y: (from.y * scale_f64).round() as i32, - }, - super::bresenham::RasterCoord { - x: (coordinate.x * scale_f64).round() as i32, - y: (coordinate.y * scale_f64).round() as i32, - }, - ); - for coord in rasteriser { - if coord.x >= 0i32 && coord.y >= 0i32 { - let x = usize::try_from(coord.x).unwrap(); - let y = usize::try_from(coord.y).unwrap(); - raster[y][x] = true; - } - } - maybe_interior_start = Some(*coordinate); - } - } - } - - let mut ascii = Vec::new(); - let row_count = raster_width.div_euclid(2); - #[expect(clippy::needless_range_loop, reason = "clippy is wrong")] - for row in 0..row_count { - let mut viewshed_line = String::new(); - for x in 0..raster_width { - let y = row * 2; - let upper = raster[y][x]; - let lower = raster[y + 1][x]; - let character = match (upper, lower) { - (true, true) => ' ', - (false, true) => '▀', - (true, false) => '▄', - (false, false) => '█', - }; - viewshed_line.push(character); - } - ascii.push(viewshed_line); - } - - ascii -} - -pub(crate) fn rasterise(mut multi_polygon: geo::MultiPolygon) -> Vec { - let width = 12u32; - let centre = f64::from(width.div_euclid(2)); - for polygons in multi_polygon.iter_mut() { - polygons.exterior_mut(|line| { - for coordinate in line.coords_mut() { - *coordinate = geo::Coord { - x: coordinate.x + centre, - y: coordinate.y + centre, - }; - } - }); - - polygons.interiors_mut(|interior| { - for interiror_line in interior { - for coordinate in interiror_line.coords_mut() { - *coordinate = geo::Coord { - x: coordinate.x + centre, - y: coordinate.y + centre, - }; - } - } - }); - } - crate::output::ascii::rasterise_multi_polygon(&multi_polygon, width) -} - -#[expect(clippy::print_stderr, reason = "This is for tests")] -pub(crate) fn assert_rasterised(actual: &[String], expected: &[&str]) { - if actual != expected { - eprintln!("Actual:"); - eprint!("{}", actual.join("\n")); - eprintln!(); - eprintln!(); - eprintln!("Expected:"); - eprint!("{}", expected.join("\n")); - eprintln!(); - panic!("Viewsheds do not match"); - } + tvs_lib::ascii::rasterise_multi_polygon_geo(&viewshed, dem.width) } diff --git a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs b/crates/total-viewsheds/src/output/viewshed.rs similarity index 89% rename from crates/total-viewsheds/src/output/viewsheds/viewshed.rs rename to crates/total-viewsheds/src/output/viewshed.rs index 9c38da3..0bfbb1b 100644 --- a/crates/total-viewsheds/src/output/viewsheds/viewshed.rs +++ b/crates/total-viewsheds/src/output/viewshed.rs @@ -6,6 +6,7 @@ use color_eyre::eyre::Result; /// point of view. The other option would be a metric projection with an anchor in the DEM centre, /// but metric projections are not globally correct. So reprojecting to the _viewshed's_ centre /// just gives us that little bit more accuracy, especially for larger DEMs. +#[cfg(test)] #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct Coordinate(pub geo::Coord); @@ -85,15 +86,59 @@ impl Viewshed<'_> { color_eyre::eyre::bail!("Point of view ({:?}) is not calculable", viewshed.pov_coord); } - let multi_polygon = crate::output::viewsheds::joiner::build_viewshed_polygon( - &segments, - viewshed.dem.scale, - )?; + let multi_polygon = Self::build_viewshed_polygon(&segments, viewshed.dem.scale)?; tracing::info!("Viewshed reconstructed in {:?}.", start.elapsed()); Ok((pov_dem_coord, multi_polygon)) } + /// Build a viewshed of euclidean polygons from polar segments. + pub(crate) fn build_viewshed_polygon( + data: &[Vec], + dem_scale: f32, + ) -> Result { + let raw_polygons = viewshed_reconstructor::joiner::Joiner::join(data, dem_scale)?; + let mut geo_polygons = Vec::new(); + for raw_polygon in raw_polygons { + let geo_polygon = Self::to_geo_polygon(&raw_polygon.to_polygon()); + tracing::trace!("Final viewshed, adding polygon: {geo_polygon:?}"); + geo_polygons.push(geo_polygon); + } + + Ok(geo::MultiPolygon::new(geo_polygons)) + } + + /// Convert the polygon to the `geo` crate's representation. Ready for exporting to `GeoJSON`. + pub(crate) fn to_geo_polygon( + polygon: &viewshed_reconstructor::polygon::Polygon, + ) -> geo::Polygon { + let holes: Vec = polygon + .interior + .iter() + .map(|hole| { + geo::LineString( + hole.iter() + .map(|coordinate| geo::Coord { + x: coordinate.x, + y: coordinate.y, + }) + .collect::>(), + ) + }) + .collect(); + + let vertices: Vec = polygon + .exterior + .iter() + .map(|coordinate| geo::Coord { + x: coordinate.x, + y: coordinate.y, + }) + .collect(); + + geo::Polygon::new(geo::LineString(vertices), holes) + } + #[expect( clippy::panic, reason = "The closures expect () so I don't think there's any other way?" @@ -189,7 +234,7 @@ impl Viewshed<'_> { #[cfg(test)] mod test { - use crate::output::ascii::assert_rasterised; + use tvs_lib::ascii::assert_rasterised; const SUMMIT_VIEWSHED: [&str; 12] = [ "████████████████████████", diff --git a/crates/total-viewsheds/src/storage/db.rs b/crates/total-viewsheds/src/storage/db.rs index 09f7794..8d52563 100644 --- a/crates/total-viewsheds/src/storage/db.rs +++ b/crates/total-viewsheds/src/storage/db.rs @@ -65,7 +65,7 @@ impl DB { pub(crate) fn load_segments( &self, id: &ID, - ) -> Result<(Vec>, i64)> { + ) -> Result<(Vec>, i64)> { tracing::debug!( "Loading polar segments for {id:?} from {:?}...", self.connection.path() @@ -118,12 +118,14 @@ impl DB { } /// Convert blob to `Segment`s. - fn bytes_to_segments(bytes: &[u8]) -> Result> { + fn bytes_to_segments(bytes: &[u8]) -> Result> { let mut out = Vec::with_capacity(bytes.len().div_euclid(4)); for chunk in bytes.chunks(4) { let array: [u8; 4] = chunk.try_into()?; #[expect(clippy::big_endian_bytes, reason = "That's how we save them in the DB")] - out.push(super::segments::Segment(u32::from_be_bytes(array))); + out.push(viewshed_reconstructor::segment::Segment( + u32::from_be_bytes(array), + )); } Ok(out) } diff --git a/crates/total-viewsheds/src/storage/segments.rs b/crates/total-viewsheds/src/storage/segments.rs index ed4a8d8..e795cfd 100644 --- a/crates/total-viewsheds/src/storage/segments.rs +++ b/crates/total-viewsheds/src/storage/segments.rs @@ -1,40 +1,6 @@ //! A "segment" is a single area of visibility for a given angle on a viewshed. There may be many //! segments per angle. -/// `Segment` is the rho portion of a line segment in polar coordinates -/// as (`rho`: u16, `delta_rho`: u16) which are packed into a single u32 for storage -#[derive(Clone, Default)] -pub(crate) struct Segment(pub u32); - -impl Segment { - /// `new` creates a `Segment` the segment's start point and the distance - pub(crate) fn new(start: u16, distance: u16) -> Self { - // pack start/distsance into a u32 in the format of (start|distance) - let wide_start: u32 = start.into(); - let wide_distance: u32 = distance.into(); - Self((wide_start << 16) | wide_distance) - } - - /// `start` returns the starting point of the `Segment` - #[expect( - clippy::as_conversions, - reason = "the top 16 bits are guaranteed to be 0" - )] - pub(crate) const fn start(&self) -> u16 { - (self.0 >> 16) as u16 - } - - /// `distance` returns the distance the `Segment` takes - #[expect( - clippy::as_conversions, - clippy::cast_possible_truncation, - reason = "the top 16 bits are guaranteed to be 0" - )] - pub(crate) const fn distance(&self) -> u16 { - self.0 as u16 - } -} - /// `PolarSegments` holds the degree of a line of sight and the list /// of visible `Segments` which is constucted through a Run Length /// Encoding algorithm. @@ -48,7 +14,7 @@ pub(crate) struct PolarSegments { pub degree: u16, /// `visible_segments` is a list of segments visible for a given /// angle and tvs id - pub visible_segments: Vec, + pub visible_segments: Vec, } impl PolarSegments { @@ -63,7 +29,8 @@ impl PolarSegments { reason = "we want to panic if out of indexes are oob" )] pub(crate) fn from_bools(degree: u16, bitmap: &[bool]) -> Self { - let mut visible_segments: Vec = Vec::with_capacity(1); + let mut visible_segments: Vec = + Vec::with_capacity(1); let char_slice: &[u8] = bytemuck::cast_slice(bitmap); @@ -72,7 +39,7 @@ impl PolarSegments { let first_zero_index = memchr::memchr(0, &char_slice[cur_index..]).unwrap_or(char_slice.len() - cur_index); - visible_segments.push(Segment::new( + visible_segments.push(viewshed_reconstructor::segment::Segment::new( u16::try_from(cur_index).expect("cur_index overflowed"), u16::try_from(first_zero_index).expect("first_zero_index overflowed"), )); diff --git a/crates/total-viewsheds/src/tests/fixtures.rs b/crates/total-viewsheds/src/tests/fixtures.rs index e554e62..022898e 100644 --- a/crates/total-viewsheds/src/tests/fixtures.rs +++ b/crates/total-viewsheds/src/tests/fixtures.rs @@ -5,36 +5,6 @@ use color_eyre::eyre::Result; /// The nodata value from NASA's SRTM data. pub(crate) const NODATA: f32 = -32768.0; -// This is a little map to help orient yourself when looking at the results: -// -// 0 1 2 3 4 5 6 7 8 -// 9 10 11 12 13 14 15 16 17 -// 18 19 20 21 22 23 24 25 26 -// 27 28 29 30 31 32 33 34 35 -// 36 37 38 39 40 41 42 43 44 -// 45 46 47 48 49 50 51 52 53 -// 54 55 56 57 58 59 60 61 62 -// 63 64 65 66 67 68 69 70 71 -// 72 73 74 75 76 77 78 79 80 - -#[rustfmt::skip] -#[inline] -#[must_use] -/// A DEM tile with a symmetrical single peak. -pub(crate) fn single_peak_dem() -> Vec { - vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 3, 3, 3, 3, 3, 1, 0, - 0, 1, 3, 6, 6, 6, 3, 1, 0, - 0, 1, 3, 6, 9, 6, 3, 1, 0, - 0, 1, 3, 6, 6, 6, 3, 1, 0, - 0, 1, 3, 3, 3, 3, 3, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] -} - // This is a little map to help orient yourself when looking at the results: // // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 diff --git a/crates/tvs-lib/src/ascii.rs b/crates/tvs-lib/src/ascii.rs new file mode 100644 index 0000000..db7f945 --- /dev/null +++ b/crates/tvs-lib/src/ascii.rs @@ -0,0 +1,137 @@ +//! Output viewsheds as ASCII. Most likely useful for tests. + +#![expect( + clippy::indexing_slicing, + clippy::expect_used, + clippy::unwrap_used, + clippy::panic, + reason = "This code is mostly for tests" +)] + +use geo::CoordsIter as _; + +/// # Panics +#[inline] +#[must_use] +pub fn rasterise_multi_polygon_geo(multi_polygon: &geo::MultiPolygon, width: u32) -> Vec { + let scale = std::env::var("ASCII_TEST_SIZE") + .unwrap_or_else(|_| "2".to_owned()) + .parse::() + .expect("ASCII_TEST_SIZE env not a valid integer"); + let scale_f64 = f64::from(scale); + let raster_width = usize::try_from(width * scale).unwrap(); + + let mut raster: Vec> = Vec::new(); + for _ in 0..width * scale { + let raster_line = vec![false; raster_width]; + raster.push(raster_line); + } + + for polygon in multi_polygon.iter() { + let mut maybe_exterior_start = None; + for coordinate in polygon.exterior_coords_iter() { + if maybe_exterior_start.is_none() { + maybe_exterior_start = Some(coordinate); + continue; + } + + let from = maybe_exterior_start.unwrap(); + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "Gotta rasterise" + )] + let rasteriser = crate::bresenham::Bresenham::new( + super::bresenham::RasterCoord { + x: (from.x * scale_f64).round() as i32, + y: (from.y * scale_f64).round() as i32, + }, + super::bresenham::RasterCoord { + x: (coordinate.x * scale_f64).round() as i32, + y: (coordinate.y * scale_f64).round() as i32, + }, + ); + for coord in rasteriser { + if coord.x >= 0i32 && coord.y >= 0i32 { + let x = usize::try_from(coord.x).unwrap(); + let y = usize::try_from(coord.y).unwrap(); + raster[y][x] = true; + } + } + maybe_exterior_start = Some(coordinate); + } + + for interiors in polygon.interiors() { + let mut maybe_interior_start = None; + for coordinate in interiors { + if maybe_interior_start.is_none() { + maybe_interior_start = Some(*coordinate); + continue; + } + + let from = maybe_interior_start.unwrap(); + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "Gotta rasterise" + )] + let rasteriser = crate::bresenham::Bresenham::new( + super::bresenham::RasterCoord { + x: (from.x * scale_f64).round() as i32, + y: (from.y * scale_f64).round() as i32, + }, + super::bresenham::RasterCoord { + x: (coordinate.x * scale_f64).round() as i32, + y: (coordinate.y * scale_f64).round() as i32, + }, + ); + for coord in rasteriser { + if coord.x >= 0i32 && coord.y >= 0i32 { + let x = usize::try_from(coord.x).unwrap(); + let y = usize::try_from(coord.y).unwrap(); + raster[y][x] = true; + } + } + maybe_interior_start = Some(*coordinate); + } + } + } + + let mut ascii = Vec::new(); + let row_count = raster_width.div_euclid(2); + #[expect(clippy::needless_range_loop, reason = "clippy is wrong")] + for row in 0..row_count { + let mut viewshed_line = String::new(); + for x in 0..raster_width { + let y = row * 2; + let upper = raster[y][x]; + let lower = raster[y + 1][x]; + let character = match (upper, lower) { + (true, true) => ' ', + (false, true) => '▀', + (true, false) => '▄', + (false, false) => '█', + }; + viewshed_line.push(character); + } + ascii.push(viewshed_line); + } + + ascii +} + +/// # Panics +#[inline] +#[expect(clippy::print_stderr, reason = "This is for tests")] +pub fn assert_rasterised(actual: &[String], expected: &[&str]) { + if actual != expected { + eprintln!("Actual:"); + eprint!("{}", actual.join("\n")); + eprintln!(); + eprintln!(); + eprintln!("Expected:"); + eprint!("{}", expected.join("\n")); + eprintln!(); + panic!("Viewsheds do not match"); + } +} diff --git a/crates/total-viewsheds/src/output/bresenham.rs b/crates/tvs-lib/src/bresenham.rs similarity index 96% rename from crates/total-viewsheds/src/output/bresenham.rs rename to crates/tvs-lib/src/bresenham.rs index 7c9109c..5d15478 100644 --- a/crates/total-viewsheds/src/output/bresenham.rs +++ b/crates/tvs-lib/src/bresenham.rs @@ -1,4 +1,4 @@ -#![cfg(test)] +//! Rasterise lines using the Bresenham algorithm. #[derive(Clone, Copy, Debug)] /// A raster coodinate. @@ -38,7 +38,7 @@ pub(crate) struct Bresenham { impl Bresenham { /// Instantiate. - pub(crate) fn new(from: RasterCoord, to: RasterCoord) -> Self { + pub(crate) const fn new(from: RasterCoord, to: RasterCoord) -> Self { let delta = RasterCoord { x: (to.x - from.x).abs(), y: (to.y - from.y).abs(), diff --git a/crates/tvs-lib/src/lib.rs b/crates/tvs-lib/src/lib.rs index cde6056..45498b0 100644 --- a/crates/tvs-lib/src/lib.rs +++ b/crates/tvs-lib/src/lib.rs @@ -1,5 +1,7 @@ //! Code we want to share amongst projects. +pub mod ascii; +mod bresenham; pub mod dem; pub mod metadata; pub mod projector; diff --git a/crates/viewshed-reconstructor/Cargo.toml b/crates/viewshed-reconstructor/Cargo.toml new file mode 100644 index 0000000..5065759 --- /dev/null +++ b/crates/viewshed-reconstructor/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "viewshed-reconstructor" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +color-eyre.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true + +[dev-dependencies] +geo.workspace = true +tvs-lib = { path = "../tvs-lib", version = "0.1.0" } + +[lints] +workspace = true + diff --git a/crates/viewshed-reconstructor/README.md b/crates/viewshed-reconstructor/README.md new file mode 100644 index 0000000..50aa1c4 --- /dev/null +++ b/crates/viewshed-reconstructor/README.md @@ -0,0 +1,5 @@ +``` +rustup target add wasm32-unknown-unknown +cargo install wasm-pack +``` + diff --git a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs b/crates/viewshed-reconstructor/src/growable_polygon.rs similarity index 94% rename from crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs rename to crates/viewshed-reconstructor/src/growable_polygon.rs index 39fb8f7..6a70294 100644 --- a/crates/total-viewsheds/src/output/viewsheds/growable_polygon.rs +++ b/crates/viewshed-reconstructor/src/growable_polygon.rs @@ -2,11 +2,11 @@ use color_eyre::eyre::{ContextCompat as _, Result, bail}; -use crate::output::viewsheds::vertices::Opening; +use crate::vertices::Opening; /// A polygon that grows, but only from its anti-clockwise facing side. #[derive(Debug, Clone)] -pub(crate) struct GrowablePolygon { +pub struct GrowablePolygon { /// The main exterior vertices. pub vertices: Vec, /// Interiore holes within the polygon. @@ -26,10 +26,7 @@ pub(crate) struct GrowablePolygon { impl GrowablePolygon { /// Create a new growable polygon. It always begins with a single polar segment converted to /// its euclidean coordinates. - pub(crate) fn new( - segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, - angle: f32, - ) -> Self { + pub(crate) fn new(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { let mut vertices = Vec::new(); for (segment_index, segment_vertex) in segment_vertices.vertices.iter().enumerate() { let opening = match segment_index { @@ -80,10 +77,7 @@ impl GrowablePolygon { /// w└──┘x w└──┘x /// /// (abcde) + (wxyz) = (wxyzcdeb) - fn new_for_insertion( - segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, - angle: f32, - ) -> Self { + fn new_for_insertion(segment_vertices: &super::segment_polygon::Vertices, angle: f32) -> Self { let mut polygon = Self::new(segment_vertices, angle); #[expect( @@ -167,7 +161,7 @@ impl GrowablePolygon { /// Insert a segment into the polygon. pub(crate) fn join_segment( &mut self, - segment_vertices: &crate::output::viewsheds::segment_polygon::Vertices, + segment_vertices: &super::segment_polygon::Vertices, vertices_range: std::ops::Range, angle: f32, ) -> Result<()> { @@ -349,25 +343,27 @@ impl GrowablePolygon { } /// Convert the polygon to the `geo` crate's representation. Ready for exporting to `GeoJSON`. - pub(crate) fn to_geo_polygon(&self) -> geo::Polygon { - let holes: Vec = self + pub fn to_polygon(&self) -> crate::polygon::Polygon { + let holes: Vec> = self .holes .iter() .map(|hole| { - geo::LineString( - hole.iter() - .map(|vertex| vertex.coordinate) - .collect::>(), - ) + hole.iter() + .map(|vertex| vertex.coordinate) + .collect::>() }) .collect(); - let vertices: Vec = self + let vertices: Vec = self .vertices .iter() .map(|vertex| vertex.coordinate) .collect(); - geo::Polygon::new(geo::LineString(vertices), holes) + + crate::polygon::Polygon { + exterior: vertices, + interior: holes, + } } /// Remove adjacent vertices that are either identical or extremely similar. This reduces the @@ -392,7 +388,10 @@ impl GrowablePolygon { } /// Are the two coordinates identical or as good as identical. - fn are_coordinates_within_tolerance(left: &geo::Coord, right: &geo::Coord) -> bool { + fn are_coordinates_within_tolerance( + left: &crate::polygon::Coordinate, + right: &crate::polygon::Coordinate, + ) -> bool { let tolerance = 1e-6f64; (left.x - right.x).abs() < tolerance && (left.y - right.y).abs() < tolerance } diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner.rs b/crates/viewshed-reconstructor/src/joiner.rs similarity index 53% rename from crates/total-viewsheds/src/output/viewsheds/joiner.rs rename to crates/viewshed-reconstructor/src/joiner.rs index 1052a7b..2119d8a 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner.rs +++ b/crates/viewshed-reconstructor/src/joiner.rs @@ -7,7 +7,7 @@ use color_eyre::Result; /// Keeps track of active and completed polygons within a viewshed. #[derive(Default)] -struct Joiner { +pub struct Joiner { /// Polygons that don't intersect with the current angle. They don't need to be checked against /// new segments. completed: Vec, @@ -18,8 +18,12 @@ struct Joiner { impl Joiner { /// Join segments into a collection of polygons. - fn join( - data: &[Vec], + /// + /// # Errors + /// If joining segments fails. + #[inline] + pub fn join( + data: &[Vec], dem_scale: f32, ) -> Result> { #[expect( @@ -48,24 +52,49 @@ impl Joiner { joiner.build_final_angle()?; joiner.move_all_active_polygons_to_completed(); + for polygon in &mut joiner.completed { + polygon.dedup_vertices_ignore_openings(); + } Ok(joiner.completed) } } -/// Build a viewshed of euclidean polygons from polar segments. -pub(crate) fn build_viewshed_polygon( - data: &[Vec], - dem_scale: f32, -) -> Result { - let raw_polygons = Joiner::join(data, dem_scale)?; - let mut geo_polygons = Vec::new(); - for mut raw_polygon in raw_polygons { - raw_polygon.dedup_vertices_ignore_openings(); - let polygon = raw_polygon.to_geo_polygon(); - tracing::trace!("Final viewshed, adding polygon: {polygon:?}"); - geo_polygons.push(polygon); +#[cfg(test)] +fn rasterise_multi_polygon( + multi_polygon: Vec, +) -> Vec { + let width = 12u32; + let centre = f64::from(width.div_euclid(2)); + + let mut multi_polygons_geo = Vec::new(); + + for polygon in multi_polygon { + let mut line_exterior = Vec::new(); + for coordinate in polygon.to_polygon().exterior { + let foo = geo::Coord { + x: coordinate.x + centre, + y: coordinate.y + centre, + }; + line_exterior.push(foo); + } + + let mut holes = Vec::new(); + for hole in polygon.to_polygon().interior { + let mut line = Vec::new(); + for coordinate in hole { + let foo = geo::Coord { + x: coordinate.x + centre, + y: coordinate.y + centre, + }; + line.push(foo); + } + holes.push(geo::LineString::from(line)); + } + + let exterior = geo::LineString::from(line_exterior); + multi_polygons_geo.push(geo::Polygon::new(exterior, holes)); } - Ok(geo::MultiPolygon::new(geo_polygons)) + tvs_lib::ascii::rasterise_multi_polygon_geo(&geo::MultiPolygon::new(multi_polygons_geo), width) } diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs b/crates/viewshed-reconstructor/src/joiner/last.rs similarity index 81% rename from crates/total-viewsheds/src/output/viewsheds/joiner/last.rs rename to crates/viewshed-reconstructor/src/joiner/last.rs index 236efea..9f2f20b 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/last.rs +++ b/crates/viewshed-reconstructor/src/joiner/last.rs @@ -3,9 +3,8 @@ //! reconstruction. use color_eyre::eyre::{ContextCompat as _, Result, bail}; -use itertools::Itertools as _; -use crate::output::viewsheds::vertices::Opening; +use crate::vertices::Opening; impl super::Joiner { /// The final angle faces the challenge of joining polygons together from the first angle. @@ -13,12 +12,9 @@ impl super::Joiner { tracing::trace!("Building viewshed for final angle"); let timing = std::time::Instant::now(); - self.active - .iter() - .sorted_by_key(|polygon| polygon.furthest_opening); + self.active.sort_by_key(|polygon| polygon.furthest_opening); self.completed - .iter() - .sorted_by_key(|polygon| polygon.furthest_opening); + .sort_by_key(|polygon| polygon.furthest_opening); let starting_polygon_indexes: Vec = self .completed @@ -202,16 +198,17 @@ impl super::Joiner { #[cfg(test)] mod test { + use tvs_lib::ascii::assert_rasterised; + + use crate::joiner::{Joiner, rasterise_multi_polygon}; + #[test] fn final_segment_joins_starting_segment_simple() { - crate::setup_logging().unwrap(); - let part = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[part.clone(), vec![], vec![], vec![], vec![], part], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + crate::setup_logging(); + let part = vec![crate::segment::Segment::new(2, 2)]; + let joined = + Joiner::join(&[part.clone(), vec![], vec![], vec![], vec![], part], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -226,28 +223,28 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn inherit_holes_final() { - crate::setup_logging().unwrap(); - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let joined = Joiner::join( &[ - vec![crate::storage::segments::Segment::new(0, 3)], + vec![crate::segment::Segment::new(0, 3)], vec![ - crate::storage::segments::Segment::new(0, 1), - crate::storage::segments::Segment::new(2, 1), + crate::segment::Segment::new(0, 1), + crate::segment::Segment::new(2, 1), ], - vec![crate::storage::segments::Segment::new(0, 3)], + vec![crate::segment::Segment::new(0, 3)], vec![], vec![], - vec![crate::storage::segments::Segment::new(0, 5)], + vec![crate::segment::Segment::new(0, 5)], ], 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████ ▀▀█████████", @@ -262,23 +259,19 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn final_segment_joins_2_starting_segments() { - crate::setup_logging().unwrap(); - let long = vec![crate::storage::segments::Segment::new(0, 4)]; + crate::setup_logging(); + let long = vec![crate::segment::Segment::new(0, 4)]; let starting = vec![ - crate::storage::segments::Segment::new(0, 1), - crate::storage::segments::Segment::new(2, 1), + crate::segment::Segment::new(0, 1), + crate::segment::Segment::new(2, 1), ]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[starting, vec![], vec![], vec![], vec![], long], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let joined = Joiner::join(&[starting, vec![], vec![], vec![], vec![], long], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -293,23 +286,20 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn final_segment_joins_starting_segment_with_2_openings() { - crate::setup_logging().unwrap(); - let long = vec![crate::storage::segments::Segment::new(0, 5)]; + crate::setup_logging(); + let long = vec![crate::segment::Segment::new(0, 5)]; let starting = vec![ - crate::storage::segments::Segment::new(2, 1), - crate::storage::segments::Segment::new(4, 1), + crate::segment::Segment::new(2, 1), + crate::segment::Segment::new(4, 1), ]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[starting, long.clone(), vec![], vec![], vec![], long], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let joined = + Joiner::join(&[starting, long.clone(), vec![], vec![], vec![], long], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████ ▀▀█████████", @@ -324,15 +314,15 @@ mod test { "████████████ ▀▀▄▄███████", "████████████▄███████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn final_segment_joins_starting_segment_complex() { - crate::setup_logging().unwrap(); - let long = vec![crate::storage::segments::Segment::new(0, 4)]; - let short = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let long = vec![crate::segment::Segment::new(0, 4)]; + let short = vec![crate::segment::Segment::new(2, 2)]; + let joined = Joiner::join( &[ short.clone(), long.clone(), @@ -346,7 +336,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -361,16 +351,16 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn a_ring() { - crate::setup_logging().unwrap(); - let part = vec![crate::storage::segments::Segment::new(2, 2)]; + crate::setup_logging(); + let part = vec![crate::segment::Segment::new(2, 2)]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon(&ring, 1.0).unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let joined = Joiner::join(&ring, 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -385,19 +375,19 @@ mod test { "███████████▄▄▄██████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn rings() { - crate::setup_logging().unwrap(); + crate::setup_logging(); let part = vec![ - crate::storage::segments::Segment::new(1, 1), - crate::storage::segments::Segment::new(3, 1), + crate::segment::Segment::new(1, 1), + crate::segment::Segment::new(3, 1), ]; let ring = vec![part; 10]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon(&ring, 1.0).unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let joined = Joiner::join(&ring, 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -412,6 +402,6 @@ mod test { "███████████▄▄▄██████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } } diff --git a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs b/crates/viewshed-reconstructor/src/joiner/normal.rs similarity index 79% rename from crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs rename to crates/viewshed-reconstructor/src/joiner/normal.rs index 0a7e9eb..ba60038 100644 --- a/crates/total-viewsheds/src/output/viewsheds/joiner/normal.rs +++ b/crates/viewshed-reconstructor/src/joiner/normal.rs @@ -10,7 +10,7 @@ impl super::Joiner { &mut self, angle: f32, angle_scale: f32, - segments: &[crate::storage::segments::Segment], + segments: &[crate::segment::Segment], dem_scale: f32, ) -> Result<()> { tracing::debug!(""); @@ -26,7 +26,7 @@ impl super::Joiner { let mut new_polygons = Vec::new(); - let segment = crate::output::viewsheds::segment_polygon::SegmentPolygon { + let segment = crate::segment_polygon::SegmentPolygon { dem_scale, angle, angle_scale, @@ -37,8 +37,7 @@ impl super::Joiner { let start = u32::from(polar_segment.start()); let end = u32::from(polar_segment.start() + polar_segment.distance()); tracing::debug!("Segment {segment_index}, distances range: {:?}", start..end); - let polygon_segment = - crate::output::viewsheds::segment_polygon::Vertices::new(&segment, start, end); + let polygon_segment = crate::segment_polygon::Vertices::new(&segment, start, end); let mut is_segment_touching_anything = false; let mut maybe_joining_polygon_index = None; let mut joined_polygons_to_remove = Vec::new(); @@ -104,17 +103,14 @@ impl super::Joiner { /// When a segment doesn't touch anything it becomes its own independent polygon. fn create_new_polygon_from_untouched_segment( - polygon_segment: &crate::output::viewsheds::segment_polygon::Vertices, + polygon_segment: &crate::segment_polygon::Vertices, angle: f32, - ) -> Result { + ) -> Result { tracing::debug!( "Segment not touching anything at angle {angle}, \ so making it its own polygon: {polygon_segment:?}" ); - let mut polygon = crate::output::viewsheds::growable_polygon::GrowablePolygon::new( - polygon_segment, - angle, - ); + let mut polygon = crate::growable_polygon::GrowablePolygon::new(polygon_segment, angle); if angle == 0.0 { polygon.is_created_at_angle_0 = true; } @@ -188,7 +184,7 @@ impl super::Joiner { fn join_segment( &mut self, growable_polygon_index: usize, - segment: &crate::output::viewsheds::segment_polygon::Vertices, + segment: &crate::segment_polygon::Vertices, maybe_joining_polygon_index: Option, angle: f32, ) -> Result { @@ -234,8 +230,8 @@ impl super::Joiner { growable_polygon_index: usize, maybe_joining_polygon_index: Option, ) -> ( - &mut crate::output::viewsheds::growable_polygon::GrowablePolygon, - Option<&mut crate::output::viewsheds::growable_polygon::GrowablePolygon>, + &mut crate::growable_polygon::GrowablePolygon, + Option<&mut crate::growable_polygon::GrowablePolygon>, ) { if let Some(joining_polygon_index) = maybe_joining_polygon_index { #[expect( @@ -261,16 +257,16 @@ impl super::Joiner { #[cfg(test)] mod test { + use tvs_lib::ascii::assert_rasterised; + + use crate::joiner::{Joiner, rasterise_multi_polygon}; + #[test] fn multiple_angles() { - crate::setup_logging().unwrap(); - let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[segment.clone(), segment.clone(), segment], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + crate::setup_logging(); + let segment = vec![crate::segment::Segment::new(0, 5)]; + let joined = Joiner::join(&[segment.clone(), segment.clone(), segment], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "█████████████████▀██████", @@ -285,18 +281,18 @@ mod test { "███████████████▄▄ ██████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn multiple_segments() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 5)]; + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 5)]; let multiple = vec![ - crate::storage::segments::Segment::new(0, 2), - crate::storage::segments::Segment::new(3, 1), + crate::segment::Segment::new(0, 2), + crate::segment::Segment::new(3, 1), ]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + let joined = Joiner::join( &[ main.clone(), main.clone(), @@ -308,7 +304,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████ ▀▀█████████", @@ -323,14 +319,14 @@ mod test { "████████▄▄▀▀█▀▀▄▄███████", "████████████▄███████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn multiple_polygons() { - crate::setup_logging().unwrap(); - let segment = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let segment = vec![crate::segment::Segment::new(0, 5)]; + let joined = Joiner::join( &[ segment.clone(), vec![], @@ -343,7 +339,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "█████████▀▀▀▀▄ █████████", @@ -358,14 +354,14 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn multiple_polygons_not_touching() { - crate::setup_logging().unwrap(); - let segment = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let segment = vec![crate::segment::Segment::new(2, 2)]; + let joined = Joiner::join( &[ segment.clone(), vec![], @@ -378,7 +374,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -393,20 +389,20 @@ mod test { "████████████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn varying_sized_segments() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 4)]; - let variance = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 4)]; + let variance = vec![crate::segment::Segment::new(0, 2)]; + let joined = Joiner::join( &[main.clone(), main.clone(), main.clone(), main, variance], 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -421,24 +417,20 @@ mod test { "██████████▄▄████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn untouched_openings_get_closed() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 4)]; + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 4)]; let pair = vec![ - crate::storage::segments::Segment::new(0, 2), - crate::storage::segments::Segment::new(3, 1), + crate::segment::Segment::new(0, 2), + crate::segment::Segment::new(3, 1), ]; - let bottom = vec![crate::storage::segments::Segment::new(0, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[main.clone(), pair, bottom, main.clone(), main], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let bottom = vec![crate::segment::Segment::new(0, 2)]; + let joined = Joiner::join(&[main.clone(), pair, bottom, main.clone(), main], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -453,15 +445,15 @@ mod test { "██████████▄▄████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn centre_is_touched_after_more_than_one_angle() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 4)]; - let top = vec![crate::storage::segments::Segment::new(2, 2)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 4)]; + let top = vec![crate::segment::Segment::new(2, 2)]; + let joined = Joiner::join( &[ main.clone(), top.clone(), @@ -473,7 +465,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -488,20 +480,17 @@ mod test { "████████████▄███████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn a_hole() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 4)]; - let lid = vec![crate::storage::segments::Segment::new(2, 1)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[main.clone(), main.clone(), lid, main.clone(), main], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 4)]; + let lid = vec![crate::segment::Segment::new(2, 1)]; + let joined = + Joiner::join(&[main.clone(), main.clone(), lid, main.clone(), main], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -516,24 +505,24 @@ mod test { "██████████▄▄████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn multiple_holes() { - crate::setup_logging().unwrap(); - let main = vec![crate::storage::segments::Segment::new(0, 6)]; + crate::setup_logging(); + let main = vec![crate::segment::Segment::new(0, 6)]; let struts = vec![ - crate::storage::segments::Segment::new(0, 1), - crate::storage::segments::Segment::new(2, 1), - crate::storage::segments::Segment::new(4, 1), + crate::segment::Segment::new(0, 1), + crate::segment::Segment::new(2, 1), + crate::segment::Segment::new(4, 1), ]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + let joined = Joiner::join( &[main.clone(), main.clone(), main.clone(), struts, main], 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████▀▀██████████████", "████████▀▄▄▄▄▄▀▀▀███████", @@ -548,18 +537,18 @@ mod test { "██████▄▀██████▀▀▀▄▄▄▄███", "███████▄▀▀▄▄▄▄██████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn segment_joins_to_multiple_polygons() { - crate::setup_logging().unwrap(); + crate::setup_logging(); let polygons = vec![ - crate::storage::segments::Segment::new(2, 1), - crate::storage::segments::Segment::new(4, 1), + crate::segment::Segment::new(2, 1), + crate::segment::Segment::new(4, 1), ]; - let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + let long = vec![crate::segment::Segment::new(0, 5)]; + let joined = Joiner::join( &[ polygons.clone(), polygons.clone(), @@ -571,7 +560,7 @@ mod test { 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "██████████▀▀ ███████████", @@ -586,28 +575,24 @@ mod test { "████████▄▄▀▀▄▀▀▄▄███████", "████████████▄███████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn join_in_distance_order() { - crate::setup_logging().unwrap(); + crate::setup_logging(); // Even though this polygon is made first and so naturally appears first in the `self.active` // list of polygons, it is in fact _nearer_ the centre and so must be joined before newer but // higher polygons. - let higher_but_made_first = crate::storage::segments::Segment::new(2, 2); + let higher_but_made_first = crate::segment::Segment::new(2, 2); - let lower = crate::storage::segments::Segment::new(0, 1); + let lower = crate::segment::Segment::new(0, 1); let first = vec![higher_but_made_first.clone()]; let second = vec![lower, higher_but_made_first]; - let long = vec![crate::storage::segments::Segment::new(0, 5)]; - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( - &[vec![], first, second, long, vec![]], - 1.0, - ) - .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let long = vec![crate::segment::Segment::new(0, 5)]; + let joined = Joiner::join(&[vec![], first, second, long, vec![]], 1.0).unwrap(); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████▀ ██████████████", @@ -622,33 +607,33 @@ mod test { "██████████▄▄████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } #[test] fn inherit_holes_common() { - crate::setup_logging().unwrap(); + crate::setup_logging(); - let joined = crate::output::viewsheds::joiner::build_viewshed_polygon( + let joined = Joiner::join( &[ vec![], - vec![crate::storage::segments::Segment::new(0, 3)], + vec![crate::segment::Segment::new(0, 3)], vec![ - crate::storage::segments::Segment::new(0, 1), - crate::storage::segments::Segment::new(2, 1), + crate::segment::Segment::new(0, 1), + crate::segment::Segment::new(2, 1), ], vec![ - crate::storage::segments::Segment::new(0, 3), - crate::storage::segments::Segment::new(4, 1), + crate::segment::Segment::new(0, 3), + crate::segment::Segment::new(4, 1), ], - vec![crate::storage::segments::Segment::new(0, 5)], + vec![crate::segment::Segment::new(0, 5)], vec![], vec![], ], 1.0, ) .unwrap(); - let actual = crate::output::ascii::rasterise(joined); + let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", "████████████████████████", @@ -663,6 +648,6 @@ mod test { "██████▄█████████████████", "████████████████████████", ]; - crate::output::ascii::assert_rasterised(&actual, &expected); + assert_rasterised(&actual, &expected); } } diff --git a/crates/viewshed-reconstructor/src/lib.rs b/crates/viewshed-reconstructor/src/lib.rs new file mode 100644 index 0000000..364482c --- /dev/null +++ b/crates/viewshed-reconstructor/src/lib.rs @@ -0,0 +1,28 @@ +//! Reconstruct viewsheds from polar segments. +//! +//! You could just use + +#[cfg(test)] +use tracing_subscriber::{Layer as _, layer::SubscriberExt as _, util::SubscriberInitExt as _}; + +mod growable_polygon; +pub mod joiner; +pub mod polygon; +pub mod segment; +mod segment_polygon; +mod vertices; + +/// Setup logging. +#[cfg(test)] +pub(crate) fn setup_logging() { + let filters = tracing_subscriber::EnvFilter::builder() + .with_default_directive( + "total_viewsheds=debug" + .parse() + .expect("Couldn't parse log ENV filter"), + ) + .from_env_lossy(); + let filter_layer = tracing_subscriber::fmt::layer().with_filter(filters); + let tracing_setup = tracing_subscriber::registry().with(filter_layer); + tracing_setup.init(); +} diff --git a/crates/viewshed-reconstructor/src/polygon.rs b/crates/viewshed-reconstructor/src/polygon.rs new file mode 100644 index 0000000..3c0a30b --- /dev/null +++ b/crates/viewshed-reconstructor/src/polygon.rs @@ -0,0 +1,39 @@ +//! A polygon represented by simple types. Avoids having to compile the `geo` crate. + +/// A viewshed-based coordinate. +#[expect(clippy::exhaustive_structs, reason = "It will never change")] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Coordinate { + /// The x coordinate. + pub x: f64, + /// The y coordinate. + pub y: f64, +} + +impl Coordinate { + /// Just a shortcut for a coordinate at 0,0. + #[inline] + #[must_use] + pub const fn zero() -> Self { + Self { x: 0.0, y: 0.0 } + } + + /// Scale the x and y compenents by a factor. + #[inline] + #[must_use] + pub fn scale(&self, factor: f64) -> Self { + Self { + x: self.x * factor, + y: self.y * factor, + } + } +} + +/// A polygon with a single exterior and multiple interior "holes". +#[expect(clippy::exhaustive_structs, reason = "It will never change")] +pub struct Polygon { + /// Coordinates representing the polygon's exterior boundary. + pub exterior: Vec, + /// Coordinates representing the polygon's interior boundaries. + pub interior: Vec>, +} diff --git a/crates/viewshed-reconstructor/src/segment.rs b/crates/viewshed-reconstructor/src/segment.rs new file mode 100644 index 0000000..0cc0e42 --- /dev/null +++ b/crates/viewshed-reconstructor/src/segment.rs @@ -0,0 +1,43 @@ +//! A "segment" is a single area of visibility for a given angle on a viewshed. There may be many +//! segments per angle. + +/// `Segment` is the rho portion of a line segment in polar coordinates +/// as (`rho`: u16, `delta_rho`: u16) which are packed into a single u32 for storage. +#[expect(clippy::exhaustive_structs, reason = "This will never change.")] +#[derive(Clone, Default)] +pub struct Segment(pub u32); + +impl Segment { + /// `new` creates a `Segment` the segment's start point and the distance + #[inline] + #[must_use] + pub fn new(start: u16, distance: u16) -> Self { + // pack start/distsance into a u32 in the format of (start|distance) + let wide_start: u32 = start.into(); + let wide_distance: u32 = distance.into(); + Self((wide_start << 16) | wide_distance) + } + + /// `start` returns the starting point of the `Segment` + #[expect( + clippy::as_conversions, + reason = "the top 16 bits are guaranteed to be 0" + )] + #[inline] + #[must_use] + pub const fn start(&self) -> u16 { + (self.0 >> 16) as u16 + } + + /// `distance` returns the distance the `Segment` takes + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "the top 16 bits are guaranteed to be 0" + )] + #[inline] + #[must_use] + pub const fn distance(&self) -> u16 { + self.0 as u16 + } +} diff --git a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs b/crates/viewshed-reconstructor/src/segment_polygon.rs similarity index 62% rename from crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs rename to crates/viewshed-reconstructor/src/segment_polygon.rs index 5b64082..75399ee 100644 --- a/crates/total-viewsheds/src/output/viewsheds/segment_polygon.rs +++ b/crates/viewshed-reconstructor/src/segment_polygon.rs @@ -5,7 +5,7 @@ pub(crate) struct Vertices { /// The 5 vertices of a segment. The 4 corners plus a copy of the first vertex to close the /// polygon. - pub vertices: [geo::Coord; 5], + pub vertices: [crate::polygon::Coordinate; 5], /// The distances of the top and bottom of the segment from the centre. pub distances: std::ops::Range, } @@ -25,11 +25,11 @@ impl Vertices { let scale = f64::from(segment.dem_scale); let vertices = [ - bottom_left * scale, - bottom_right * scale, - top_right * scale, - top_left * scale, - bottom_left * scale, + bottom_left.scale(scale), + bottom_right.scale(scale), + top_right.scale(scale), + top_left.scale(scale), + bottom_left.scale(scale), ]; Self { @@ -51,14 +51,14 @@ pub(crate) struct SegmentPolygon { impl SegmentPolygon { /// Convert an index along a line of sight into a coordinate. - fn index_to_coordinate(&self, index: u32) -> super::viewshed::Coordinate { + fn index_to_coordinate(&self, index: u32) -> crate::polygon::Coordinate { let radians = self.angle.to_radians(); let distance = f64::from(index); - super::viewshed::Coordinate(geo::coord! { + crate::polygon::Coordinate { x: distance * f64::from(radians.cos()), - y: distance * f64::from(radians.sin()) - }) + y: distance * f64::from(radians.sin()), + } } /// Rotate a point about the centre of the viewshed. @@ -66,26 +66,25 @@ impl SegmentPolygon { clippy::suboptimal_flops, reason = "I think readability is more important?" )] - fn rotate_by(point: super::viewshed::Coordinate, angle: f64) -> geo::Coord { - let dx = point.0.x; - let dy = point.0.y; + fn rotate_by(point: crate::polygon::Coordinate, angle: f64) -> crate::polygon::Coordinate { + let dx = point.x; + let dy = point.y; let cos = angle.to_radians().cos(); let sin = angle.to_radians().sin(); - geo::coord! { + crate::polygon::Coordinate { x: dx * cos - dy * sin, - y: dx * sin + dy * cos + y: dx * sin + dy * cos, } } } #[cfg(test)] mod test { - fn builder( - viewshed: &crate::output::viewsheds::viewshed::Viewshed, - angle: f32, - ) -> crate::output::viewsheds::segment_polygon::SegmentPolygon { - crate::output::viewsheds::segment_polygon::SegmentPolygon { - dem_scale: viewshed.dem.scale, + use super::*; + + fn builder(angle: f32) -> SegmentPolygon { + SegmentPolygon { + dem_scale: 1.0, angle, angle_scale: 1.0, } @@ -93,44 +92,40 @@ mod test { #[derive(Debug)] struct VisiblePolygonFor { - pov: geo::Coord, angle: f32, opening_index: u32, closing_index: u32, } - fn make_visible_polygon_for(setup: &VisiblePolygonFor) -> Vec { - let dem = crate::run::test::make_dem(&crate::tests::fixtures::single_peak_dem()); - let viewshed = crate::output::viewsheds::viewshed::Viewshed { - dem: &dem, - pov_coord: tvs_lib::dem::Coordinate(setup.pov), - }; - let segment = builder(&viewshed, setup.angle); + fn make_visible_polygon_for(setup: &VisiblePolygonFor) -> Vec { + let segment = builder(setup.angle); let vertices = super::Vertices::new(&segment, setup.opening_index, setup.closing_index).vertices; - let polygon = geo::Polygon::new(geo::LineString(vertices.into()), vec![]); let mut polygon_as_dem_coords = Vec::new(); - for coord in &polygon.exterior().0 { - let converted_coord = viewshed - .convert_viewshed_coord_to_dem_coord( - crate::output::viewsheds::viewshed::Coordinate(*coord), - ) - .unwrap(); - polygon_as_dem_coords.push(round_coordinate(converted_coord)); + for vertex in &vertices { + let dem_coord = crate::polygon::Coordinate { + x: vertex.x + 4.0, + y: -vertex.y + 4.0, + }; + polygon_as_dem_coords.push(round_coordinate(dem_coord)); } polygon_as_dem_coords } + pub(crate) fn coord(x: f64, y: f64) -> crate::polygon::Coordinate { + crate::polygon::Coordinate { x, y } + } + fn round(float: f64) -> f64 { let factor = 10f64.powi(7); (float * factor).round() / factor } - fn round_coordinate(coordinate: geo::Coord) -> geo::Coord { - geo::coord! { - x: round(coordinate.x), - y: round(coordinate.y), + fn round_coordinate(coordinate: crate::polygon::Coordinate) -> crate::polygon::Coordinate { + crate::polygon::Coordinate { + x: round(coordinate.x), + y: round(coordinate.y), } } @@ -150,29 +145,27 @@ mod test { mod from_centre_to_top_right { use super::*; - const POV: geo::Coord = geo::coord! {x: 4.0, y: 4.0}; const ANGLE: f32 = 45.0; // The polygon we're making is `abcd` from the above guide. + #[expect(clippy::unreadable_literal, reason = "It's just a test")] #[test] fn making_a_visible_polygon() { assert_eq!( make_visible_polygon_for(&VisiblePolygonFor { - pov: POV, angle: ANGLE, opening_index: 1, closing_index: 2, }), vec![ - (4.7009079, 3.2867515), - (4.7132491, 3.2990927), - (5.4264998, 2.5981837), - (5.4018174, 2.5735014), - (4.7009079, 3.2867515) + coord(4.7009093, 3.2867496), + coord(4.7132504, 3.2990907), + coord(5.4265009, 2.5981815), + coord(5.4018185, 2.5734991), + coord(4.7009093, 3.2867496), ] .into_iter() - .map(Into::into) - .collect::>() + .collect::>() ); } } @@ -184,38 +177,36 @@ mod test { // 1 . . . . . . . . . // 2 . . . . . . . . . // 3 . . . . . . . . . + // 5 . . . . o .a . . . + // 6 . . . . . ( .d . . + // 7 . . . . . b. ) . . + // 8 . . . . . . c. . . // 4 . . . . . . . . . - // 5 . . . o .a . . . . - // 6 . . . . ( .d . . . - // 7 . . . . b. ) . . . - // 8 . . . . . c. . . . // mod from_bottom_left_to_bottom_right { use super::*; - const POV: geo::Coord = geo::coord! {x: 3.0, y: 5.0}; const ANGLE: f32 = 135.0 + 180.0; // The polygon we're making is `abcd` from the above guide. + #[expect(clippy::unreadable_literal, reason = "It's just a test")] #[test] fn making_a_visible_polygon() { assert_eq!( make_visible_polygon_for(&VisiblePolygonFor { - pov: POV, angle: ANGLE, opening_index: 1, closing_index: 2, }), vec![ - (3.7132486, 5.7009105), - (3.7009074, 5.7132517), - (4.4018163, 6.4265025), - (4.4264987, 6.4018201), - (3.7132486, 5.7009105) + coord(4.7132503, 4.7009094), + coord(4.7009091, 4.7132506), + coord(5.4018183, 5.4265011), + coord(5.4265006, 5.4018187), + coord(4.7132503, 4.7009094), ] .into_iter() - .map(Into::into) - .collect::>() + .collect::>() ); } } diff --git a/crates/total-viewsheds/src/output/viewsheds/vertices.rs b/crates/viewshed-reconstructor/src/vertices.rs similarity index 95% rename from crates/total-viewsheds/src/output/viewsheds/vertices.rs rename to crates/viewshed-reconstructor/src/vertices.rs index d212c98..34249ab 100644 --- a/crates/total-viewsheds/src/output/viewsheds/vertices.rs +++ b/crates/viewshed-reconstructor/src/vertices.rs @@ -3,9 +3,9 @@ /// A vertex is a single point in a polygon. #[derive(Debug, Clone)] -pub(crate) struct Vertex { +pub struct Vertex { /// The coordinate of the vertex in euclidian space. - pub coordinate: geo::Coord, + pub coordinate: crate::polygon::Coordinate, /// The kind of opening that this vertex represents. pub opening: Opening, } @@ -22,7 +22,7 @@ impl Vertex { /// The `u32` values in the variants are for storing the polar distance from the centre. This saves /// having to do trigonometry to figure out if two openings are touching. #[derive(Eq, PartialEq, Debug, Clone)] -pub(crate) enum Opening { +pub enum Opening { /// No opening. We could also just use `Option::None`, but unwrapping it isn't so ergonomic. Null, /// This i for polygons that start at 0 degrees. It's possible that another polygon (or even @@ -124,11 +124,10 @@ pub(crate) fn find_contact( joining distances: {joining_opening_range:?}", ); - let is_touching = - crate::output::viewsheds::growable_polygon::GrowablePolygon::is_touching( - base_opening_range, - joining_opening_range, - ); + let is_touching = crate::growable_polygon::GrowablePolygon::is_touching( + base_opening_range, + joining_opening_range, + ); if is_touching { tracing::debug!("🟢 Openings touch"); @@ -153,7 +152,7 @@ mod test { fn vertex(opening: super::Opening) -> Vertex { super::Vertex { - coordinate: geo::Coord::zero(), + coordinate: crate::polygon::Coordinate::zero(), opening, } } From 698c69769e5b299728143ba78bd8c33788d7871f Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Sat, 11 Jul 2026 12:19:05 +0700 Subject: [PATCH 7/7] feat: WASM-compilable viewshed reconstructor Successfully used in the browser. --- .github/workflows/ci.yaml | 4 + Cargo.lock | 71 ++++++++-- crates/total-viewsheds/src/output/viewshed.rs | 8 +- crates/viewshed-reconstructor/Cargo.toml | 8 +- crates/viewshed-reconstructor/README.md | 13 ++ .../src/growable_polygon.rs | 54 ++++---- crates/viewshed-reconstructor/src/joiner.rs | 11 +- .../viewshed-reconstructor/src/joiner/last.rs | 90 ++++++------ .../src/joiner/normal.rs | 96 ++++++------- crates/viewshed-reconstructor/src/lib.rs | 7 + crates/viewshed-reconstructor/src/vertices.rs | 3 + crates/viewshed-reconstructor/src/wasm.rs | 130 ++++++++++++++++++ 12 files changed, 339 insertions(+), 156 deletions(-) create mode 100644 crates/viewshed-reconstructor/src/wasm.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index adc09b1..96e6c1a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,6 +44,10 @@ jobs: run: cargo build --verbose --all - name: Run tests run: cargo nextest run + - name: Install wasm-pack + uses: taiki-e/install-action@wasm-pack + - name: Build WASM + run: wasm-pack build --target web crates/viewshed-reconstructor e2e: name: End to end tests diff --git a/Cargo.lock b/Cargo.lock index 21d5772..ba0dacd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "console_log" version = "1.0.0" @@ -414,6 +424,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "gdal" version = "0.19.0" @@ -755,11 +789,12 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1239,6 +1274,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -1489,11 +1530,13 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" name = "viewshed-reconstructor" version = "0.1.0" dependencies = [ - "color-eyre", + "console_error_panic_hook", "geo", + "js-sys", "tracing", "tracing-subscriber", "tvs-lib", + "wasm-bindgen", ] [[package]] @@ -1507,9 +1550,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1520,9 +1563,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1530,9 +1573,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -1543,18 +1586,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/crates/total-viewsheds/src/output/viewshed.rs b/crates/total-viewsheds/src/output/viewshed.rs index 0bfbb1b..0a40077 100644 --- a/crates/total-viewsheds/src/output/viewshed.rs +++ b/crates/total-viewsheds/src/output/viewshed.rs @@ -86,7 +86,7 @@ impl Viewshed<'_> { color_eyre::eyre::bail!("Point of view ({:?}) is not calculable", viewshed.pov_coord); } - let multi_polygon = Self::build_viewshed_polygon(&segments, viewshed.dem.scale)?; + let multi_polygon = Self::build_viewshed_polygon(&segments, viewshed.dem.scale); tracing::info!("Viewshed reconstructed in {:?}.", start.elapsed()); Ok((pov_dem_coord, multi_polygon)) @@ -96,8 +96,8 @@ impl Viewshed<'_> { pub(crate) fn build_viewshed_polygon( data: &[Vec], dem_scale: f32, - ) -> Result { - let raw_polygons = viewshed_reconstructor::joiner::Joiner::join(data, dem_scale)?; + ) -> geo::MultiPolygon { + let raw_polygons = viewshed_reconstructor::joiner::Joiner::join(data, dem_scale); let mut geo_polygons = Vec::new(); for raw_polygon in raw_polygons { let geo_polygon = Self::to_geo_polygon(&raw_polygon.to_polygon()); @@ -105,7 +105,7 @@ impl Viewshed<'_> { geo_polygons.push(geo_polygon); } - Ok(geo::MultiPolygon::new(geo_polygons)) + geo::MultiPolygon::new(geo_polygons) } /// Convert the polygon to the `geo` crate's representation. Ready for exporting to `GeoJSON`. diff --git a/crates/viewshed-reconstructor/Cargo.toml b/crates/viewshed-reconstructor/Cargo.toml index 5065759..68c8f39 100644 --- a/crates/viewshed-reconstructor/Cargo.toml +++ b/crates/viewshed-reconstructor/Cargo.toml @@ -6,11 +6,15 @@ edition = "2024" [lib] crate-type = ["cdylib", "rlib"] -[dependencies] -color-eyre.workspace = true +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing.workspace = true tracing-subscriber.workspace = true +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.7" +js-sys = "0.3.103" +wasm-bindgen = "0.2" + [dev-dependencies] geo.workspace = true tvs-lib = { path = "../tvs-lib", version = "0.1.0" } diff --git a/crates/viewshed-reconstructor/README.md b/crates/viewshed-reconstructor/README.md index 50aa1c4..34a8772 100644 --- a/crates/viewshed-reconstructor/README.md +++ b/crates/viewshed-reconstructor/README.md @@ -1,5 +1,18 @@ +# Viewshed Reconstruction + +This crate is for reconstucting viewsheds from their raw "polar segments". + +It is used for both in the Rust backend for outputting final viewsheds. And it is used in browser +frontends for rendering viewsheds in maps. + +How to compile for WASM: ``` rustup target add wasm32-unknown-unknown cargo install wasm-pack + +wasm-pack build \ + --target web \ + --out-dir /publicish/Workspace/viewview/website/src/lib/viewshed-reconstructor \ + crates/viewshed-reconstructor ``` diff --git a/crates/viewshed-reconstructor/src/growable_polygon.rs b/crates/viewshed-reconstructor/src/growable_polygon.rs index 6a70294..9fb7562 100644 --- a/crates/viewshed-reconstructor/src/growable_polygon.rs +++ b/crates/viewshed-reconstructor/src/growable_polygon.rs @@ -1,7 +1,5 @@ //! Growable polygons are polygons constructed one angle at a time from polar segments. -use color_eyre::eyre::{ContextCompat as _, Result, bail}; - use crate::vertices::Opening; /// A polygon that grows, but only from its anti-clockwise facing side. @@ -100,18 +98,19 @@ impl GrowablePolygon { /// polygon as follows: /// * `Opening::NewStart/NewEnd` become `Opening::Start/End`. /// * `Opening::Start/End` become `Opening::Null`. - pub(crate) fn downgrade_openings(&mut self) -> Result<()> { + pub(crate) fn downgrade_openings(&mut self) { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Downgrading openings"); let mut iterator = self.vertices.iter_mut().rev(); while let Some(vertex) = iterator.next() { match vertex.opening { Opening::Null | Opening::GenesisStart(_) | Opening::GenesisEnd(_) => {} Opening::End(_) => { - bail!("Dangling `Opening`"); + panic!("Dangling `Opening`"); } Opening::Start(_) => { let Some(next) = iterator.next() else { - bail!("`Opening::Start` without adjacent end"); + panic!("`Opening::Start` without adjacent end"); }; vertex.opening = Opening::Null; @@ -125,8 +124,6 @@ impl GrowablePolygon { } } } - - Ok(()) } /// Insert new vertices into the polygon. Can be either a segment or an entire other growable @@ -139,8 +136,8 @@ impl GrowablePolygon { &mut self, base_vertices_range: std::ops::Range, joining_vertices: Vec, - ) -> Result<()> { - let old_start = self.extract_old_start(base_vertices_range.end)?; + ) { + let old_start = self.extract_old_start(base_vertices_range.end); let removed_vertices: Vec = self .vertices @@ -149,13 +146,11 @@ impl GrowablePolygon { self.vertices .get_mut(base_vertices_range.start) - .context("Couldn't get new opening start index")? + .expect("Couldn't get new opening start index") .opening = old_start; self.dedup_vertices_but_keep_openings(); self.create_holes(&removed_vertices); - - Ok(()) } /// Insert a segment into the polygon. @@ -164,20 +159,19 @@ impl GrowablePolygon { segment_vertices: &super::segment_polygon::Vertices, vertices_range: std::ops::Range, angle: f32, - ) -> Result<()> { + ) { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Splicing new segment ({:?}) at: {vertices_range:?}", segment_vertices.distances ); let segment = Self::new_for_insertion(segment_vertices, angle); - self.join(vertices_range, segment.vertices)?; + self.join(vertices_range, segment.vertices); if segment.furthest_opening < self.furthest_opening { self.furthest_opening = segment.furthest_opening; } - - Ok(()) } /// Insert another polygon into the `self` polygon, where `self` isn't the final polygon. @@ -201,7 +195,7 @@ impl GrowablePolygon { &mut self, base_vertices_range: std::ops::Range, joining_polygon: &mut Self, - ) -> Result<()> { + ) { let mut maybe_joining_new_end_index = None; for (index, vertex) in joining_polygon.vertices.iter_mut().enumerate().rev() { // Find where in the joining polygon we are opening up to be joined by the base polygon. @@ -218,7 +212,7 @@ impl GrowablePolygon { } } let Some(joining_new_end_index) = maybe_joining_new_end_index else { - bail!("Couldn't find `Opening::NewEnd` for joining polygon"); + panic!("Couldn't find `Opening::NewEnd` for joining polygon"); }; // We can assume that the vertex at which we join the incoming polygon is always 1 before @@ -229,12 +223,13 @@ impl GrowablePolygon { // is joined. joining_polygon.vertices.rotate_left(joining_vertices_entry); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Splicing existing polygon at {base_vertices_range:?}, \ joining polygon: {joining_polygon:#?}, rotated at {joining_vertices_entry}", ); - self.join(base_vertices_range, joining_polygon.vertices.clone())?; + self.join(base_vertices_range, joining_polygon.vertices.clone()); self.holes.extend(joining_polygon.holes.clone()); @@ -245,8 +240,6 @@ impl GrowablePolygon { if joining_polygon.is_created_at_angle_0 { self.is_created_at_angle_0 = true; } - - Ok(()) } /// Insert a starting polygon (from angle 0) into a final polygon (from angle ~360). @@ -255,7 +248,8 @@ impl GrowablePolygon { base_vertices_range: std::ops::Range, joining_vertices_range: std::ops::Range, joining_polygon: &mut Self, - ) -> Result<()> { + ) { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Splicing final polygon at {base_vertices_range:?}, \ with: {:#?} at {joining_vertices_range:?}", @@ -266,12 +260,12 @@ impl GrowablePolygon { joining_polygon .vertices .get_mut(joining_vertices_range.start) - .context("Bad joining vertex index")? + .expect("Bad joining vertex index") .opening = Opening::Null; joining_polygon .vertices .get_mut(joining_vertices_range.end) - .context("Bad joining vertex index")? + .expect("Bad joining vertex index") .opening = Opening::Null; // Rotating achieves the effect of unlooping the polygon at the point at which the polygon @@ -280,11 +274,9 @@ impl GrowablePolygon { .vertices .rotate_left(joining_vertices_range.start); - self.join(base_vertices_range, joining_polygon.vertices.clone())?; + self.join(base_vertices_range, joining_polygon.vertices.clone()); self.holes.extend(joining_polygon.holes.clone()); - - Ok(()) } /// Join a polygon into itself. @@ -299,6 +291,7 @@ impl GrowablePolygon { right_range.start..left_range.start }; + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Splicing polygon into itself at: {range:?} ({left_range:?}/{right_range:?})" ); @@ -309,16 +302,16 @@ impl GrowablePolygon { } /// Extract the starting vertex of an opening that has just been joined to. - pub(crate) fn extract_old_start(&mut self, index: usize) -> Result { + pub(crate) fn extract_old_start(&mut self, index: usize) -> Opening { let vertex = self .vertices .get_mut(index) - .context("Bad index for old opening start")?; + .expect("Bad index for old opening start"); let old_start = vertex.opening.clone(); vertex.opening = Opening::Null; - Ok(old_start) + old_start } /// Create interior holes from the vertices that were removed for the join. @@ -327,6 +320,7 @@ impl GrowablePolygon { for hole in holes { if hole.iter().any(|vertex| !vertex.is_centre()) { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Hole: {hole:?}"); self.holes.push(hole.to_vec()); } diff --git a/crates/viewshed-reconstructor/src/joiner.rs b/crates/viewshed-reconstructor/src/joiner.rs index 2119d8a..fd02d57 100644 --- a/crates/viewshed-reconstructor/src/joiner.rs +++ b/crates/viewshed-reconstructor/src/joiner.rs @@ -3,8 +3,6 @@ mod last; mod normal; -use color_eyre::Result; - /// Keeps track of active and completed polygons within a viewshed. #[derive(Default)] pub struct Joiner { @@ -22,10 +20,11 @@ impl Joiner { /// # Errors /// If joining segments fails. #[inline] + #[must_use] pub fn join( data: &[Vec], dem_scale: f32, - ) -> Result> { + ) -> Vec { #[expect( clippy::as_conversions, clippy::cast_precision_loss, @@ -47,16 +46,16 @@ impl Joiner { )] let angle = anglish as f32 / angle_scale; - joiner.build_angle(angle, angle_scale, segments, dem_scale)?; + joiner.build_angle(angle, angle_scale, segments, dem_scale); } - joiner.build_final_angle()?; + joiner.build_final_angle(); joiner.move_all_active_polygons_to_completed(); for polygon in &mut joiner.completed { polygon.dedup_vertices_ignore_openings(); } - Ok(joiner.completed) + joiner.completed } } diff --git a/crates/viewshed-reconstructor/src/joiner/last.rs b/crates/viewshed-reconstructor/src/joiner/last.rs index 9f2f20b..a5f2f3f 100644 --- a/crates/viewshed-reconstructor/src/joiner/last.rs +++ b/crates/viewshed-reconstructor/src/joiner/last.rs @@ -2,16 +2,14 @@ //! complicated, but it's okay if the code is less efficient as it only happens once per viewshed //! reconstruction. -use color_eyre::eyre::{ContextCompat as _, Result, bail}; - use crate::vertices::Opening; impl super::Joiner { /// The final angle faces the challenge of joining polygons together from the first angle. - pub(crate) fn build_final_angle(&mut self) -> Result<()> { + pub(crate) fn build_final_angle(&mut self) { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Building viewshed for final angle"); - let timing = std::time::Instant::now(); self.active.sort_by_key(|polygon| polygon.furthest_opening); self.completed .sort_by_key(|polygon| polygon.furthest_opening); @@ -24,20 +22,22 @@ impl super::Joiner { .map(|item| item.0) .collect(); - tracing::debug!(""); - let total_polygons = starting_polygon_indexes.len() + self.active.len(); - tracing::debug!("Final angle, polygons to check: {}", total_polygons); + #[cfg(not(target_arch = "wasm32"))] + { + tracing::debug!(""); + let total_polygons = starting_polygon_indexes.len() + self.active.len(); + tracing::debug!("Final angle, polygons to check: {}", total_polygons); + tracing::trace!( + "Final angle, completed polygon (started at angle 0) indices: {:?}. Active polygons: {:?}", + starting_polygon_indexes, + self.active.len() + ); + }; - tracing::trace!( - "Final angle, completed polygon (started at angle 0) indices: {:?}. Active polygons: {:?}", - starting_polygon_indexes, - self.active.len() - ); let mut touching_starting_polygons = Vec::new(); for starting_polygon_index in starting_polygon_indexes { for final_polygon_index in 0..self.active.len() { - if self - .handle_touching_polygons(final_polygon_index, Some(starting_polygon_index))? + if self.handle_touching_polygons(final_polygon_index, Some(starting_polygon_index)) { touching_starting_polygons.push(starting_polygon_index); } @@ -45,6 +45,7 @@ impl super::Joiner { } let polygons_to_remove = touching_starting_polygons.iter().rev(); + #[cfg(not(target_arch = "wasm32"))] tracing::debug!( "Removing final joined polygons: {:?}", polygons_to_remove.clone().collect::>() @@ -62,12 +63,8 @@ impl super::Joiner { .collect(); for self_joining_polygon_index in self_joining_polygon_indexes { - self.handle_touching_polygons(self_joining_polygon_index, None)?; + self.handle_touching_polygons(self_joining_polygon_index, None); } - - tracing::debug!("Final angle done in {:?}", timing.elapsed()); - - Ok(()) } /// Check whether a final and a starting polygon are touching. @@ -75,7 +72,8 @@ impl super::Joiner { &mut self, final_polygon_index: usize, maybe_starting_polygon_index: Option, - ) -> Result { + ) -> bool { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!( "Checking final polygon {final_polygon_index} \ against starting polygon {maybe_starting_polygon_index:?}" @@ -84,13 +82,13 @@ impl super::Joiner { let vertices_clone = if let Some(starting_polygon_index) = maybe_starting_polygon_index { self.completed .get_mut(starting_polygon_index) - .context("Bad polygon index")? + .expect("Bad polygon index") .vertices .clone() } else { self.active .get_mut(final_polygon_index) - .context("Bad polygon index")? + .expect("Bad polygon index") .vertices .clone() }; @@ -104,18 +102,19 @@ impl super::Joiner { | Opening::Start(_) | Opening::End(_) => (), Opening::GenesisStart(_) => { - bail!("Dangling `Opening::GenesisStart`"); + panic!("Dangling `Opening::GenesisStart`"); } Opening::GenesisEnd(start) => { let Some(next) = iterator.next() else { - bail!("`Opening::GenesisEnd` without a following vertex"); + panic!("`Opening::GenesisEnd` without a following vertex"); }; let Opening::GenesisStart(end) = next.1.opening else { + #[cfg(not(target_arch = "wasm32"))] tracing::error!( "Bad opening ({:?}) in polygon: {vertices_clone:#?}", next.1.opening ); - bail!("`Opening::GenesisEnd` not followed by `Opening::GenesisStart`"); + panic!("`Opening::GenesisEnd` not followed by `Opening::GenesisStart`"); }; let next_index = next.0; @@ -128,17 +127,17 @@ impl super::Joiner { joining_distances_range, maybe_starting_polygon_index, joining_vertices_range, - )?; + ); if is_touching && maybe_starting_polygon_index.is_some() { - self.handle_touching_polygons(final_polygon_index, None)?; - return Ok(true); + self.handle_touching_polygons(final_polygon_index, None); + return true; } } } } - Ok(false) + false } /// Join a final polygon to either itself or a starting polygon. @@ -148,7 +147,8 @@ impl super::Joiner { joining_opening_range: std::ops::Range, maybe_joining_polygon_index: Option, joining_vertices_range: std::ops::Range, - ) -> Result { + ) -> bool { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!( "Checking base polygon {base_polygon_index} \ opening indices: {joining_vertices_range:?}" @@ -157,7 +157,7 @@ impl super::Joiner { let base_polygon = self .active .get_mut(base_polygon_index) - .context("Bad polygon index")?; + .expect("Bad polygon index"); #[expect( clippy::indexing_slicing, @@ -169,30 +169,34 @@ impl super::Joiner { let Some(base_vertices_range) = super::super::vertices::find_contact(&base_polygon.vertices, &joining_opening_range) else { - return Ok(false); + return false; }; match maybe_joining_polygon { Some(joining_polygon) => { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Final angle, polygon BEFORE: {:#?}", base_polygon); base_polygon.join_starting_polygon( base_vertices_range, joining_vertices_range, joining_polygon, - )?; + ); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Final angle, polygon AFTER joined polygon: {:#?}", base_polygon ); } None => { + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Final angle, self-polygon BEFORE: {:#?}", base_polygon); base_polygon.join_self(joining_vertices_range, base_vertices_range); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!("Final angle, self-polygon AFTER: {:#?}", base_polygon); } } - Ok(true) + true } } @@ -206,8 +210,7 @@ mod test { fn final_segment_joins_starting_segment_simple() { crate::setup_logging(); let part = vec![crate::segment::Segment::new(2, 2)]; - let joined = - Joiner::join(&[part.clone(), vec![], vec![], vec![], vec![], part], 1.0).unwrap(); + let joined = Joiner::join(&[part.clone(), vec![], vec![], vec![], vec![], part], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -242,8 +245,7 @@ mod test { vec![crate::segment::Segment::new(0, 5)], ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -270,7 +272,7 @@ mod test { crate::segment::Segment::new(0, 1), crate::segment::Segment::new(2, 1), ]; - let joined = Joiner::join(&[starting, vec![], vec![], vec![], vec![], long], 1.0).unwrap(); + let joined = Joiner::join(&[starting, vec![], vec![], vec![], vec![], long], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -297,8 +299,7 @@ mod test { crate::segment::Segment::new(2, 1), crate::segment::Segment::new(4, 1), ]; - let joined = - Joiner::join(&[starting, long.clone(), vec![], vec![], vec![], long], 1.0).unwrap(); + let joined = Joiner::join(&[starting, long.clone(), vec![], vec![], vec![], long], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -334,8 +335,7 @@ mod test { short, ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -359,7 +359,7 @@ mod test { crate::setup_logging(); let part = vec![crate::segment::Segment::new(2, 2)]; let ring = vec![part; 10]; - let joined = Joiner::join(&ring, 1.0).unwrap(); + let joined = Joiner::join(&ring, 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -386,7 +386,7 @@ mod test { crate::segment::Segment::new(3, 1), ]; let ring = vec![part; 10]; - let joined = Joiner::join(&ring, 1.0).unwrap(); + let joined = Joiner::join(&ring, 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", diff --git a/crates/viewshed-reconstructor/src/joiner/normal.rs b/crates/viewshed-reconstructor/src/joiner/normal.rs index ba60038..0c54bea 100644 --- a/crates/viewshed-reconstructor/src/joiner/normal.rs +++ b/crates/viewshed-reconstructor/src/joiner/normal.rs @@ -2,8 +2,6 @@ //! are the ones that occur for every angle other than the final one. Therefore these should be //! simpler and faster. -use color_eyre::eyre::Result; - impl super::Joiner { /// Build the viewshed for a single angle. pub(crate) fn build_angle( @@ -12,15 +10,18 @@ impl super::Joiner { angle_scale: f32, segments: &[crate::segment::Segment], dem_scale: f32, - ) -> Result<()> { - tracing::debug!(""); - tracing::debug!("Building viewshed for angle: {angle}"); - tracing::debug!( - "Polygon counts, completed: {}, active: {}. Segments: {}", - self.completed.len(), - self.active.len(), - segments.len() - ); + ) { + #[cfg(not(target_arch = "wasm32"))] + { + tracing::debug!(""); + tracing::debug!("Building viewshed for angle: {angle}"); + tracing::debug!( + "Polygon counts, completed: {}, active: {}. Segments: {}", + self.completed.len(), + self.active.len(), + segments.len() + ); + }; self.prepare_active_polygons(); @@ -32,10 +33,10 @@ impl super::Joiner { angle_scale, }; - let timing = std::time::Instant::now(); for (segment_index, polar_segment) in segments.iter().enumerate() { let start = u32::from(polar_segment.start()); let end = u32::from(polar_segment.start() + polar_segment.distance()); + #[cfg(not(target_arch = "wasm32"))] tracing::debug!("Segment {segment_index}, distances range: {:?}", start..end); let polygon_segment = crate::segment_polygon::Vertices::new(&segment, start, end); let mut is_segment_touching_anything = false; @@ -43,14 +44,12 @@ impl super::Joiner { let mut joined_polygons_to_remove = Vec::new(); for active_polygon_index in 0..self.active.len() { - let touchging_timing = std::time::Instant::now(); - let is_segment_touching_this_polygon = self.join_segment( active_polygon_index, &polygon_segment, maybe_joining_polygon_index, angle, - )?; + ); #[expect( clippy::indexing_slicing, @@ -69,13 +68,9 @@ impl super::Joiner { } else { maybe_joining_polygon_index = None; } - - tracing::debug!( - "Segment checked against active {active_polygon_index} in {:?}", - touchging_timing.elapsed() - ); } + #[cfg(not(target_arch = "wasm32"))] tracing::debug!("Removing joined polygons: {joined_polygons_to_remove:?}"); for joined_polygon_index in joined_polygons_to_remove.iter().rev() { self.active.remove(*joined_polygon_index); @@ -85,27 +80,24 @@ impl super::Joiner { new_polygons.push(Self::create_new_polygon_from_untouched_segment( &polygon_segment, angle, - )?); + )); } } for polygon in &mut self.active { - polygon.downgrade_openings()?; + polygon.downgrade_openings(); } - tracing::debug!("Angle {angle} done in {:?}", timing.elapsed()); - self.move_untouched_active_polygons_to_completed(); self.active.extend(new_polygons); - - Ok(()) } /// When a segment doesn't touch anything it becomes its own independent polygon. fn create_new_polygon_from_untouched_segment( polygon_segment: &crate::segment_polygon::Vertices, angle: f32, - ) -> Result { + ) -> crate::growable_polygon::GrowablePolygon { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!( "Segment not touching anything at angle {angle}, \ so making it its own polygon: {polygon_segment:?}" @@ -114,9 +106,9 @@ impl super::Joiner { if angle == 0.0 { polygon.is_created_at_angle_0 = true; } - polygon.downgrade_openings()?; + polygon.downgrade_openings(); - Ok(polygon) + polygon } /// Set all active polygons to untouched and order them based on their openings' distance from @@ -187,10 +179,11 @@ impl super::Joiner { segment: &crate::segment_polygon::Vertices, maybe_joining_polygon_index: Option, angle: f32, - ) -> Result { + ) -> bool { let (base_polygon, maybe_joining_polygon) = self.get_involved_polygons(growable_polygon_index, maybe_joining_polygon_index); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Polygon {growable_polygon_index} BEFORE: {:#?}", base_polygon @@ -199,19 +192,21 @@ impl super::Joiner { let Some(base_vertices_range) = super::super::vertices::find_contact(&base_polygon.vertices, &segment.distances) else { - return Ok(false); + return false; }; match maybe_joining_polygon { Some(joining_polygon) => { - base_polygon.join_non_starting_polygon(base_vertices_range, joining_polygon)?; + base_polygon.join_non_starting_polygon(base_vertices_range, joining_polygon); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Polygon {growable_polygon_index} AFTER joined polygon: {:#?}", base_polygon ); } None => { - base_polygon.join_segment(segment, base_vertices_range, angle)?; + base_polygon.join_segment(segment, base_vertices_range, angle); + #[cfg(not(target_arch = "wasm32"))] tracing::trace!( "Polygon {growable_polygon_index} AFTER joined segment: {:#?}", base_polygon @@ -219,7 +214,7 @@ impl super::Joiner { } } - Ok(true) + true } /// Get the polygons involved in a touch check. @@ -265,7 +260,7 @@ mod test { fn multiple_angles() { crate::setup_logging(); let segment = vec![crate::segment::Segment::new(0, 5)]; - let joined = Joiner::join(&[segment.clone(), segment.clone(), segment], 1.0).unwrap(); + let joined = Joiner::join(&[segment.clone(), segment.clone(), segment], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -302,8 +297,7 @@ mod test { main, ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -337,8 +331,7 @@ mod test { vec![], ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -372,8 +365,7 @@ mod test { vec![], ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -400,8 +392,7 @@ mod test { let joined = Joiner::join( &[main.clone(), main.clone(), main.clone(), main, variance], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -429,7 +420,7 @@ mod test { crate::segment::Segment::new(3, 1), ]; let bottom = vec![crate::segment::Segment::new(0, 2)]; - let joined = Joiner::join(&[main.clone(), pair, bottom, main.clone(), main], 1.0).unwrap(); + let joined = Joiner::join(&[main.clone(), pair, bottom, main.clone(), main], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -463,8 +454,7 @@ mod test { main, ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -488,8 +478,7 @@ mod test { crate::setup_logging(); let main = vec![crate::segment::Segment::new(0, 4)]; let lid = vec![crate::segment::Segment::new(2, 1)]; - let joined = - Joiner::join(&[main.clone(), main.clone(), lid, main.clone(), main], 1.0).unwrap(); + let joined = Joiner::join(&[main.clone(), main.clone(), lid, main.clone(), main], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -520,8 +509,7 @@ mod test { let joined = Joiner::join( &[main.clone(), main.clone(), main.clone(), struts, main], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████▀▀██████████████", @@ -558,8 +546,7 @@ mod test { vec![], ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -591,7 +578,7 @@ mod test { let first = vec![higher_but_made_first.clone()]; let second = vec![lower, higher_but_made_first]; let long = vec![crate::segment::Segment::new(0, 5)]; - let joined = Joiner::join(&[vec![], first, second, long, vec![]], 1.0).unwrap(); + let joined = Joiner::join(&[vec![], first, second, long, vec![]], 1.0); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", @@ -631,8 +618,7 @@ mod test { vec![], ], 1.0, - ) - .unwrap(); + ); let actual = rasterise_multi_polygon(joined); let expected = [ "████████████████████████", diff --git a/crates/viewshed-reconstructor/src/lib.rs b/crates/viewshed-reconstructor/src/lib.rs index 364482c..66b6473 100644 --- a/crates/viewshed-reconstructor/src/lib.rs +++ b/crates/viewshed-reconstructor/src/lib.rs @@ -2,6 +2,12 @@ //! //! You could just use +#![expect( + clippy::expect_used, + clippy::panic, + reason = "We're not using errors to keep the WASM bundle size small" +)] + #[cfg(test)] use tracing_subscriber::{Layer as _, layer::SubscriberExt as _, util::SubscriberInitExt as _}; @@ -11,6 +17,7 @@ pub mod polygon; pub mod segment; mod segment_polygon; mod vertices; +mod wasm; /// Setup logging. #[cfg(test)] diff --git a/crates/viewshed-reconstructor/src/vertices.rs b/crates/viewshed-reconstructor/src/vertices.rs index 34249ab..0dd2a4b 100644 --- a/crates/viewshed-reconstructor/src/vertices.rs +++ b/crates/viewshed-reconstructor/src/vertices.rs @@ -117,6 +117,7 @@ pub(crate) fn find_contact( ) -> Option> { OpeningsIterator::new(vertices) .filter(|(base_opening_index, base_opening_range)| { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!( "Checking if openings touch: \ index: {base_opening_index:?}, \ @@ -130,8 +131,10 @@ pub(crate) fn find_contact( ); if is_touching { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!("🟢 Openings touch"); } else { + #[cfg(not(target_arch = "wasm32"))] tracing::debug!("🟡 Openings don't touch"); } diff --git a/crates/viewshed-reconstructor/src/wasm.rs b/crates/viewshed-reconstructor/src/wasm.rs new file mode 100644 index 0000000..72182dc --- /dev/null +++ b/crates/viewshed-reconstructor/src/wasm.rs @@ -0,0 +1,130 @@ +//! Support using this crate in the browser with WASM. + +// #![cfg(target_arch = "wasm32")] + +#[cfg(target_arch = "wasm32")] +use js_sys::Array; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +/// WASM only supports simple types, so this is a simple representation of a polygon. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct PlainPolygon { + /// The exterior coordinates of the polygon. x/y components are flattened, so the first + /// coordinate is found at [0,1]. + exterior: Vec, + /// The interior coordinates of the polygon. To find the starting/ending indices of each + /// hole see `hole_indices`. x/y components are flattened, so the first coordinate is + /// found at [0,1]. + interiors: Vec, + /// Indices of where each hole starts and ends in `interiors`. + hole_indices: Vec, +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl PlainPolygon { + /// Getter for `exterior`. + #[wasm_bindgen(getter)] + #[must_use] + pub fn exterior(&self) -> Vec { + self.exterior.clone() + } + + /// Getter for `interiors`. + #[wasm_bindgen(getter)] + #[must_use] + pub fn interiors(&self) -> Vec { + self.interiors.clone() + } + + /// Getter for `hole_indices`. + #[wasm_bindgen(getter)] + #[must_use] + pub fn hole_indices(&self) -> Vec { + self.hole_indices.clone() + } +} + +#[cfg(target_arch = "wasm32")] +impl PlainPolygon { + /// Instantiate. + #[inline] + #[must_use] + pub fn new(growable_polygon: &crate::growable_polygon::GrowablePolygon) -> Self { + let mut exterior = Vec::with_capacity(growable_polygon.vertices.len() * 2); + for vertex in &growable_polygon.vertices { + exterior.push(vertex.coordinate.x); + exterior.push(vertex.coordinate.y); + } + + let mut interiors = Vec::new(); + let mut hole_indices = Vec::with_capacity(growable_polygon.holes.len()); + + for hole in &growable_polygon.holes { + hole_indices.push( + u32::try_from(interiors.len()).expect("Couldn't cast `interiors.len()` to `u32`"), + ); + + for vertex in hole { + interiors.push(vertex.coordinate.x); + interiors.push(vertex.coordinate.y); + } + } + + Self { + exterior, + interiors, + hole_indices, + } + } +} + +/// Reconstruct a viewshed from raw polar segments. +/// +/// # Panics +/// When reconstructing the viewshed fails. +#[expect( + unreachable_pub, + reason = "We won't it to be `pub` for WASM but not avaiable outside the crate" +)] +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +#[must_use] +pub fn reconstruct(js_data: &js_sys::Array, dem_scale: f32) -> js_sys::Array { + console_error_panic_hook::set_once(); + let mut rust_data: Vec> = Vec::new(); + + for segments_for_angle in js_data.iter() { + let inner_js_array: js_sys::Array = segments_for_angle.unchecked_into(); + let mut inner_rust_vec = Vec::new(); + + for segment in inner_js_array.iter() { + #[expect( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::as_conversions, + reason = " + We're only worried about the truncation here, but it's very unlikely. + Remember that this value is actually a bitpack of 2 `u16`s. + " + )] + let bitpack = segment + .as_f64() + .expect("Expected valid JavaScript `number`") as u32; + + inner_rust_vec.push(crate::segment::Segment(bitpack)); + } + rust_data.push(inner_rust_vec); + } + + let growable_polygons = crate::joiner::Joiner::join(&rust_data, dem_scale); + + let js_array = Array::new(); + for growable_polygon in growable_polygons { + let plain_polygon = PlainPolygon::new(&growable_polygon); + js_array.push(&JsValue::from(plain_polygon)); + } + js_array +}