From 832568aa0044d063aa8450c2a080b0d8768c7e69 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 28 Feb 2024 18:58:40 +0100 Subject: [PATCH] Completed camera control server in lua and more work on merging algorithm --- frontend/website/src/map/map.ts | 7 +- scripts/lua/camera/OlympusCameraControl.lua | 214 ++++++++++++++++++ .../python/map_generator/.vscode/launch.json | 2 +- .../python/map_generator/airbases_to_kml.py | 37 +++ .../configs/Caucasus/HighResolution.yml | 5 + .../LowResolution.kml} | 47 +--- .../configs/Caucasus/LowResolution.yml | 5 + .../configs/Caucasus/MediumResolution.kml | 71 ++++++ .../configs/Caucasus/MediumResolution.yml | 5 + .../configs/Caucasus/airbases.json | 1 + .../configs/Caucasus/airbases.kml | 1 + .../map_generator/configs/LasVegas/config.yml | 6 - scripts/python/map_generator/main.py | 15 +- scripts/python/map_generator/map_generator.py | 52 +++-- 14 files changed, 403 insertions(+), 65 deletions(-) create mode 100644 scripts/lua/camera/OlympusCameraControl.lua create mode 100644 scripts/python/map_generator/airbases_to_kml.py create mode 100644 scripts/python/map_generator/configs/Caucasus/HighResolution.yml rename scripts/python/map_generator/configs/{LasVegas/boundary.kml => Caucasus/LowResolution.kml} (52%) create mode 100644 scripts/python/map_generator/configs/Caucasus/LowResolution.yml create mode 100644 scripts/python/map_generator/configs/Caucasus/MediumResolution.kml create mode 100644 scripts/python/map_generator/configs/Caucasus/MediumResolution.yml create mode 100644 scripts/python/map_generator/configs/Caucasus/airbases.json create mode 100644 scripts/python/map_generator/configs/Caucasus/airbases.kml delete mode 100644 scripts/python/map_generator/configs/LasVegas/config.yml diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts index 6629c59c..33daa13f 100644 --- a/frontend/website/src/map/map.ts +++ b/frontend/website/src/map/map.ts @@ -582,7 +582,8 @@ export class Map extends L.Map { setCameraControlMode(newCameraControlMode: string) { this.#cameraControlMode = newCameraControlMode; - this.#broadcastPosition(); + if (this.#slaveDCSCamera) + this.#broadcastPosition(); } /* Event handlers */ @@ -773,7 +774,7 @@ export class Map extends L.Map { try { groundElevation = parseFloat(response); var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", `http://localhost:${this.#cameraControlPort}`); + xmlHttp.open("PUT", `http://127.0.0.1:${this.#cameraControlPort}`); xmlHttp.setRequestHeader("Content-Type", "application/json"); const C = 40075016.686; @@ -981,7 +982,7 @@ export class Map extends L.Map { #checkCameraPort(){ var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("OPTIONS", `http://localhost:${this.#cameraControlPort}`); + xmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`); xmlHttp.onload = (res: any) => { if (xmlHttp.status == 200) this.#setSlaveDCSCameraAvailable(true); diff --git a/scripts/lua/camera/OlympusCameraControl.lua b/scripts/lua/camera/OlympusCameraControl.lua new file mode 100644 index 00000000..5fa4894c --- /dev/null +++ b/scripts/lua/camera/OlympusCameraControl.lua @@ -0,0 +1,214 @@ +local _prevLuaExportStart = LuaExportStart +local _prevLuaExportBeforeNextFrame = LuaExportBeforeNextFrame +local _prevLuaExportStop = LuaExportStop + +local server = nil +local port = 3003 +local headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: PUT, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\nKeep-Alive: timeout=2, max=100\r\nConnection: Keep-Alive\r\n\r\n" + +function startTCPServer() + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'Starting TCP Server') + package.path = package.path..";"..lfs.currentdir().."/LuaSocket/?.lua" + package.cpath = package.cpath..";"..lfs.currentdir().."/LuaSocket/?.dll" + + socket = require("socket") + + server = assert(socket.bind("127.0.0.1", port)) + if server then + server:setoption("tcp-nodelay", true) + server:settimeout(0) + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'TCP Server listening on port ' .. port) + else + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'TCP Server did not start successfully') + end +end + +function receiveTCP() + if server then + -- Accept a new connection without blocking + local client = server:accept() + + if client then + -- Set the timeout of the connection to 5ms + client:settimeout(0) + client:setoption("tcp-nodelay", true) + + local acc = "" + local data = "" + + -- Start receiving data, accumulate it in acc + while data ~= nil do + -- Receive a new line + data, err, partial = client:receive('*l') + if data then + -- If we receive an empty string it means the header section of the message is over + if data == "" then + -- Is this an OPTIONS request? + if string.find(acc, "OPTIONS") ~= nil then + client:send("HTTP/1.1 200 OK\r\n" .. headers) + client:close() + + -- Is this a PUT request? + elseif string.find(acc, "PUT") ~= nil then + -- Extract the length of the body + local contentLength = string.match(acc, "Content%-Length: (%d+)") + if contentLength ~= nil then + -- Receive the body + body, err, partial = client:receive(tonumber(contentLength)) + if body ~= nil then + local lat = string.match(body, '"lat":%s*([%+%-]?[%d%.]+)%s*[},]') + local lng = string.match(body, '"lng":%s*([%+%-]?[%d%.]+)%s*[},]') + local alt = string.match(body, '"alt":%s*([%+%-]?[%d%.]+)%s*[},]') + local mode = string.match(body, '"mode":%s*"(%a+)"%s*[},]') + + if lat ~= nil and lng ~= nil then + client:send("HTTP/1.1 200 OK\r\n" .. headers) + + local position = {} + position["lat"] = tonumber(lat) + position["lng"] = tonumber(lng) + if alt ~= nil then + position["alt"] = tonumber(alt) + end + + -- F11 view + if mode == "live" or mode == nil then + LoSetCommand(158) + -- F10 view + elseif mode == "map" then + LoSetCommand(15) + end + + client:send(setCameraPosition(position)) + client:close() + else + client:send("HTTP/1.1 500 ERROR\r\n" .. headers) + client:close() + end + else + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.ERROR, err) + end + end + client:close() + break + end + else + -- Keep accumulating the incoming data + acc = acc .. " " .. data + end + end + end + end + end +end + +function stopTCPServer() + if server then + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'Stopping TCP Server') + server:close() + end + server = nil +end + +function setCameraPosition(position) + -- Get the old camera position + local oldPos = LoGetCameraPosition() + + -- Extract the commanded position + local point = LoGeoCoordinatesToLoCoordinates(position.lng, position.lat) + local pointNorth = LoGeoCoordinatesToLoCoordinates(position.lng, position.lat + 0.1) + + -- Compute the local map rotation and scale and send it back to the server + local rotation = math.atan2(pointNorth.z - point.z, pointNorth.x - point.x) + + -- If no altitude is provided, preserve the current camera altitude + local altitude = nil + if position.alt == nil then + altitude = oldPos.p.y + else + altitude = position.alt + end + + -- Set the camera position + local pos = + { + x = {x = 0, y = -1, z = 0}, + y = {x = 1, y = 0, z = 0}, + z = {x = 0, y = 0, z = 1}, + p = {x = point.x, y = altitude, z = point.z} + } + LoSetCameraPosition(pos) + + return '{"northRotation": ' .. rotation .. '}' +end + +LuaExportStart = function() + package.path = package.path..";"..lfs.currentdir().."/LuaSocket/?.lua" + package.cpath = package.cpath..";"..lfs.currentdir().."/LuaSocket/?.dll" + + startTCPServer() + + -- call original + if _prevLuaExportStart then + _status, _result = pcall(_prevLuaExportStart) + if not _status then + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.ERROR, 'ERROR Calling other LuaExportStart from another script', _result) + end + end +end + +LuaExportBeforeNextFrame = function() + receiveTCP() + + -- call original + if _prevLuaExportBeforeNextFrame then + _status, _result = pcall(_prevLuaExportBeforeNextFrame) + if not _status then + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.ERROR, 'ERROR Calling other LuaExportBeforeNextFrame from another script', _result) + end + end +end + +LuaExportStop = function() + stopTCPServer() + + -- call original + if _prevLuaExportStop then + _status, _result = pcall(_prevLuaExportStop) + if not _status then + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.ERROR, 'ERROR Calling other LuaExportStop from another script', _result) + end + end +end + +function serializeTable(val, name, skipnewlines, depth) + skipnewlines = skipnewlines or false + depth = depth or 0 + + local tmp = string.rep(" ", depth) + if name then + if type(name) == "number" then + tmp = tmp .. "[" .. name .. "]" .. " = " + else + tmp = tmp .. name .. " = " + end + end + + if type(val) == "table" then + tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") + for k, v in pairs(val) do + tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") + end + tmp = tmp .. string.rep(" ", depth) .. "}" + elseif type(val) == "number" then + tmp = tmp .. tostring(val) + elseif type(val) == "string" then + tmp = tmp .. string.format("%q", val) + elseif type(val) == "boolean" then + tmp = tmp .. (val and "true" or "false") + else + tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" + end + + return tmp +end \ No newline at end of file diff --git a/scripts/python/map_generator/.vscode/launch.json b/scripts/python/map_generator/.vscode/launch.json index acdc83b5..40a86611 100644 --- a/scripts/python/map_generator/.vscode/launch.json +++ b/scripts/python/map_generator/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "program": "main.py", "console": "integratedTerminal", - "args": ["./configs/NTTR/config.yml"] + "args": ["./configs/Caucasus/HighResolution.yml"] } ] } \ No newline at end of file diff --git a/scripts/python/map_generator/airbases_to_kml.py b/scripts/python/map_generator/airbases_to_kml.py new file mode 100644 index 00000000..69e23881 --- /dev/null +++ b/scripts/python/map_generator/airbases_to_kml.py @@ -0,0 +1,37 @@ +import sys +from fastkml import kml +from pygeoif.geometry import Polygon +import json +import math + +# constants +C = 40075016.686 # meters, Earth equatorial circumference +R = C / (2 * math.pi) # meters, Earth equatorial radius +W = 10000 # meters, size of the square around the airbase + +if len(sys.argv) == 1: + print("Please provide a json file as first argument. You can also drop the json file on this script to run it.") +else: + input_file = sys.argv[1] + k = kml.KML() + ns = '{http://www.opengis.net/kml/2.2}' + + d = kml.Document(ns, 'docid', 'doc name', 'doc description') + k.append(d) + + with open(input_file) as jp: + j = json.load(jp) + + for point in j['airbases'].values(): + p = kml.Placemark(ns, 'id', 'name', 'description') + lat = point['latitude'] + lng = point['longitude'] + + latDelta = math.degrees(W / R) + lngDelta = math.degrees(W / (R * math.cos(math.radians(lat)))) + + p.geometry = Polygon([(lng - lngDelta, lat - latDelta), (lng - lngDelta, lat + latDelta), (lng + lngDelta, lat + latDelta), (lng + lngDelta, lat - latDelta)]) + d.append(p) + + with open(input_file.removesuffix('.json')+'.kml', 'w') as kp: + kp.writelines(k.to_string(prettyprint=True)) \ No newline at end of file diff --git a/scripts/python/map_generator/configs/Caucasus/HighResolution.yml b/scripts/python/map_generator/configs/Caucasus/HighResolution.yml new file mode 100644 index 00000000..135d85c9 --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/HighResolution.yml @@ -0,0 +1,5 @@ +{ + 'output_directory': '.\Caucasus', # Where to save the output files + 'boundary_file': '.\configs\Caucasus\airbases.kml', # Input kml file setting the boundary of the map to create + 'zoom_factor': 0.1 # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] +} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/LasVegas/boundary.kml b/scripts/python/map_generator/configs/Caucasus/LowResolution.kml similarity index 52% rename from scripts/python/map_generator/configs/LasVegas/boundary.kml rename to scripts/python/map_generator/configs/Caucasus/LowResolution.kml index b2da4f16..e56c3210 100644 --- a/scripts/python/map_generator/configs/LasVegas/boundary.kml +++ b/scripts/python/map_generator/configs/Caucasus/LowResolution.kml @@ -2,7 +2,7 @@ Senza titolo - + - + - + normal - #__managed_style_1FB08D70372F0ACE3361 + #__managed_style_1EB9027B622F24E92C22 highlight - #__managed_style_2F9039BE1B2F0ACE3361 + #__managed_style_280E5494AE2F24E92C22 - + Poligono senza titolo - -115.0437621195802 - 36.2404454323581 - 568.1069300877758 + 37.25019544589698 + 44.41771380726969 + -138.6844933247498 0 0 35 - 21582.08160380367 + 3831683.119853139 absolute - #__managed_style_0152B588AD2F0ACE3361 + #__managed_style_0F57E9B9782F24E92C22 - -115.0657741984423,36.20908708202413,0 -115.0064821223275,36.20969233542438,0 -115.0077003574054,36.25251471885595,0 -115.0604905801644,36.25236266770626,0 -115.0657741984423,36.20908708202413,0 - - - - - - - Poligono senza titolo - - -115.144039076036 - 36.07599823986274 - 636.6854074835677 - 0 - 0 - 35 - 14114.14487087633 - absolute - - #__managed_style_0152B588AD2F0ACE3361 - - - - - -115.1866576903507,36.06787728722109,0 -115.1107872218999,36.06765965614163,0 -115.1145241760975,36.10294242539007,0 -115.1779590799479,36.10179027153036,0 -115.1866576903507,36.06787728722109,0 + 32.46459319237173,45.67416695848307,0 32.2740650283415,45.2221541106433,0 33.22174616520244,44.4837859435444,0 34.05427109764131,44.2149221586376,0 34.96485577272431,44.60230684639296,0 35.50552864748745,44.8069362633187,0 36.446105774871,44.84425518198143,0 36.76914203317659,44.70347050722764,0 38.22313992004164,44.3163345847565,0 39.43106567523965,43.72064977016311,0 40.23832274382622,43.06831352526857,0 41.01327578994438,42.67925159935859,0 41.34464189582403,42.34329512558789,0 41.16749495371268,41.74956946999534,0 40.80780496107725,41.39360013128164,0 39.98364177441992,41.27272565351572,0 39.42209428526464,41.27830763089842,0 38.82136897872954,41.2291415593637,0 38.78900701766597,39.59331113999448,0 46.4826445997655,39.11657164682355,0 46.83937081793388,45.04996086829865,0 46.88987497227086,47.59122144470205,0 32.29992865035658,47.73230965442627,0 32.46459319237173,45.67416695848307,0 diff --git a/scripts/python/map_generator/configs/Caucasus/LowResolution.yml b/scripts/python/map_generator/configs/Caucasus/LowResolution.yml new file mode 100644 index 00000000..1190bb55 --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/LowResolution.yml @@ -0,0 +1,5 @@ +{ + 'output_directory': '.\Caucasus', # Where to save the output files + 'boundary_file': '.\configs\Caucasus\LowResolution.kml', # Input kml file setting the boundary of the map to create + 'zoom_factor': 0.5 # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] +} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/Caucasus/MediumResolution.kml b/scripts/python/map_generator/configs/Caucasus/MediumResolution.kml new file mode 100644 index 00000000..0e0af764 --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/MediumResolution.kml @@ -0,0 +1,71 @@ + + + + MediumResolution.kml + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl + + + + + + Untitled Polygon + + #m_ylw-pushpin + + + 1 + + + + 38.01314831290035,44.57640274670221,0 38.02944798685579,44.60276751290017,0 38.03701060313551,44.62238817868698,0 38.04339313778571,44.65487861016632,0 38.05976583140756,44.68123948836164,0 38.07725859098449,44.69472890923019,0 38.11291945378741,44.71522618625848,0 38.15737096473566,44.74243269722512,0 38.18485778769824,44.74972736437217,0 38.21177330355324,44.7634527979471,0 38.25852533281954,44.76484292008565,0 38.30469494342632,44.77265044108603,0 38.36049540323182,44.77428878950431,0 38.37905726470693,44.77483376304828,0 38.44346421266869,44.78314952437485,0 38.48003274711677,44.79063943163983,0 38.53510271283257,44.79863898344369,0 38.58131400320148,44.79993344687284,0 38.62750840123165,44.80120844370346,0 38.67367469674257,44.80246384096735,0 38.71956774499493,44.80373436400996,0 38.75627384860339,44.8047373881825,0 38.81181562123516,44.79979181843787,0 38.86732496532647,44.79482114270701,0 38.9418190787013,44.78384375272389,0 38.97905705406094,44.778337408217,0 39.01583176131165,44.77923974267458,0 39.0900956709648,44.76819193321122,0 39.15465996521237,44.76330766478745,0 39.21909989645339,44.75839879172977,0 39.2560739657125,44.75283091433155,0 39.2926368410373,44.75366325960852,0 39.32996917677399,44.74166417489695,0 39.36650552718523,44.74247384431043,0 39.41255496817574,44.73705800868364,0 39.48634199995926,44.72579712136418,0 39.52357053143431,44.71374682534395,0 39.56077169452355,44.70168758531198,0 39.6165192307209,44.6835853772921,0 39.65364919828341,44.67150732324989,0 39.72813801276361,44.64093207562432,0 39.78387993140787,44.61639917038017,0 39.83048040841024,44.591681936124,0 39.85859564812953,44.57301236646423,0 39.90509751878192,44.5482824053089,0 39.94249273127954,44.52338201672428,0 39.99853360189767,44.49238758170668,0 40.06418699118991,44.44876421256014,0 40.10122416737037,44.43022041947975,0 40.17476642160124,44.39952250587424,0 40.22062991958054,44.38112056606379,0 40.26616925085745,44.36908408773458,0 40.32066183181299,44.3571725102825,0 40.35687691238554,44.35134432568741,0 40.44767908225873,44.32717767100264,0 40.48389607700778,44.31496053921349,0 40.53788603259777,44.30297929770608,0 40.57403888305044,44.29073121970499,0 40.66420062160682,44.26643647458618,0 40.70947171289453,44.24790062395415,0 40.79051702894591,44.22341319737066,0 40.86240131168533,44.1987807551584,0 40.90727896521627,44.18655120898199,0 40.9971751422766,44.16204307598525,0 41.06896675719418,44.14366887573048,0 41.11381283684614,44.13137235210612,0 41.19430400444456,44.10665219373025,0 41.29215003328483,44.09472243976229,0 41.37240316383965,44.07625022448003,0 41.4257513549977,44.07025961185919,0 41.47917541902734,44.05789539392341,0 41.5325417114993,44.04550988906617,0 41.58597268867194,44.02675551936349,0 41.63036154961326,44.02063436986167,0 41.66587473645689,44.01445730447657,0 41.70144949563171,44.00192621497413,0 41.73692804647133,43.99573038959692,0 41.80790527475083,43.9769682848858,0 41.86104680287534,43.96445030267287,0 41.92294354557144,43.95192640497554,0 41.97593555038175,43.94569501888316,0 42.0289322801191,43.92675381975371,0 42.06422510564282,43.92045601376109,0 42.11715008872746,43.9078193193867,0 42.16126121663363,43.89517408007984,0 42.20531402728533,43.88883194377493,0 42.24050364188762,43.8761279474099,0 42.27567975880997,43.86974938516271,0 42.33721825822707,43.85697758262555,0 42.36356945565706,43.84425223576203,0 42.4162746612844,43.83145551598434,0 42.46900614324979,43.81867159050743,0 42.52171597341432,43.80587848838739,0 42.57435452102627,43.79303476282518,0 42.66192800537871,43.76098283129826,0 42.68820542361732,43.75451828141484,0 42.71453056569531,43.75437460048707,0 42.78443169369056,43.7223423950142,0 42.82815553677322,43.70943149863041,0 42.88931539666483,43.69006393459333,0 42.93306263868907,43.68341230544033,0 42.97662716861461,43.66407134484366,0 43.01149955281491,43.65107862194047,0 43.0724574902484,43.62514140546799,0 43.13327577066558,43.59283128307425,0 43.17650639536976,43.56075387900956,0 43.19390304524044,43.5542383074794,0 43.2458228621912,43.52203453680247,0 43.28917624236355,43.50260877389707,0 43.33249712773732,43.48316752413342,0 43.39341545401486,43.46982012002935,0 43.41934387933852,43.45687177914835,0 43.45417023053265,43.45011914908257,0 43.54116031694757,43.42340105939786,0 43.60215378404467,43.40973324821089,0 43.62805859606298,43.39668681257595,0 43.66269445936168,43.3835061348825,0 43.71445259635788,43.35737376950652,0 43.75781252139677,43.34400401633088,0 43.79224026980383,43.32441967291912,0 43.84412456960271,43.30452957746165,0 43.86977478309283,43.28506457890185,0 43.90414446493419,43.26544051056777,0 43.98147797545967,43.22607034290247,0 44.05860864936233,43.18675114853992,0 44.06709119023878,43.18027612980465,0 44.12715980003196,43.15386409427541,0 44.18743101444286,43.13372968109404,0 44.23897569124552,43.11371864078352,0 44.27358790844354,43.1066733857835,0 44.28203952535908,43.100179325582,0 44.33401294247151,43.09279825752913,0 44.37696649117978,43.07928586699428,0 44.41989828391778,43.06575823137832,0 44.45469479565277,43.06501638277474,0 44.51539689936585,43.05076982497799,0 44.55018766980033,43.04346891719553,0 44.58498367893472,43.0361552674791,0 44.63705094972993,43.02198827645994,0 44.68065391179591,43.02090890830104,0 44.73259892455201,43.01331904594272,0 44.7761165148564,43.01226353824702,0 44.83675394474802,43.0044120900956,0 44.87127327746453,42.9971914930625,0 44.92355181162227,42.99582138878485,0 44.97588472069648,42.99439652359818,0 45.02831258656551,42.99289450840476,0 45.07207630821009,42.99158817485907,0 45.0895848496222,42.99106070586604,0 45.14117239315028,42.97694647097289,0 45.17601296980932,42.97596816214897,0 45.21047622192639,42.96865722217818,0 45.28886690434192,42.96639262525014,0 45.34984222945756,42.96459406473044,0 45.37646173654968,42.9700880705898,0 45.43810803374696,42.97443956886776,0 45.49185301851502,42.9851743808321,0 45.52759443680108,42.99020242274158,0 45.5633579633161,42.99522156765907,0 45.62609326432623,43.00557429659632,0 45.64419236937005,43.01125368180472,0 45.71617312145327,43.0276362621879,0 45.76976054364151,43.03847417679859,0 45.83259009413669,43.05533668327431,0 45.88664835623321,43.0725031755841,0 45.90460015950028,43.0782529571539,0 45.96724185845013,43.09518163430661,0 46.02071293056959,43.10603674537837,0 46.08363829327891,43.12283810442124,0 46.12919552359966,43.1402092420311,0 46.17527504653226,43.16392574078856,0 46.21256602738341,43.18797952343618,0 46.25038909630553,43.21839048242535,0 46.28777139407883,43.2424397908981,0 46.30774824868674,43.27361498596171,0 46.32865779774432,43.31754539001604,0 46.33935284365015,43.34270001952713,0 46.34117604912893,43.36820735506829,0 46.34482783840426,43.41922482374558,0 46.33913196765758,43.46421581299708,0 46.32316096412696,43.49041999939335,0 46.30715550915618,43.51662177371173,0 46.28265395288795,43.54954566985638,0 46.26660295085507,43.57574025590465,0 46.2416351531661,43.60227192587565,0 46.21623782737858,43.62240514869328,0 46.1908274697064,43.64252979654582,0 46.14844641588729,43.67605152819741,0 46.09754206819095,43.71624568019379,0 46.04573101337119,43.7436522532048,0 45.99352467916157,43.76465044035011,0 45.93237929007829,43.78592394921217,0 45.89713232779982,43.79351371317505,0 45.86228431553677,43.80746104708328,0 45.77472350124277,43.83590173707711,0 45.71348831641006,43.85703490795119,0 45.65191189201103,43.87174753144312,0 45.61667402158427,43.87922934910029,0 45.54656786024061,43.90052206107319,0 45.49350967028987,43.90850130248388,0 45.45862584676529,43.92229280860784,0 45.42337035734356,43.92970702709297,0 45.38811307759509,43.937108252938,0 45.35322471819191,43.95085957802508,0 45.3005649870871,43.96510331400013,0 45.23011659328166,43.97979736377869,0 45.17709837174289,43.98760694827055,0 45.15110443452078,44.00104251565386,0 45.08951712248168,44.01540345578517,0 45.06316511801389,44.02245559755142,0 44.99267086992596,44.03698583304246,0 44.92219608741352,44.05146730436935,0 44.89615719435373,44.06483501784728,0 44.8170642195509,44.08581884549502,0 44.79988885185894,44.09895260738689,0 44.76462446697382,44.10613318475036,0 44.70326861737696,44.1266187753704,0 44.65106302010911,44.15323163062371,0 44.58076324644311,44.17383446229861,0 44.53684318558219,44.18748404076401,0 44.50180424794954,44.20093302105131,0 44.46701746015775,44.22072797580508,0 44.42304975061145,44.23432878609736,0 44.38822310457682,44.25409621098623,0 44.33556878516728,44.27418909606034,0 44.30069641113407,44.29392636141888,0 44.28339349889821,44.30696393893064,0 44.23097562347358,44.33336747822818,0 44.17851176458034,44.35974675030393,0 44.15226167318346,44.37292711611524,0 44.11709253571063,44.3862520301023,0 44.06408773145306,44.3998586550974,0 44.02020359643429,44.41966316702641,0 43.97607632487119,44.43308694714862,0 43.95846260821069,44.43972355886389,0 43.90559771591774,44.45962297435154,0 43.87925266250725,44.47274148280075,0 43.83525174753024,44.49247153785101,0 43.76445414868348,44.51255406389881,0 43.72038648542567,44.53223910895665,0 43.64934486886363,44.54587768081366,0 43.61397852496307,44.55904554271575,0 43.56057970837746,44.56605714778042,0 43.49855667429589,44.58587055533157,0 43.42755914078424,44.60574627274126,0 43.32971303148774,44.62584438952917,0 43.28529868203722,44.63900032613098,0 43.24967933417494,44.64569501002083,0 43.18749309794997,44.66535857889014,0 43.16090537909993,44.67832317668243,0 43.11641904880359,44.69143181586064,0 43.08075073451171,44.69808695876216,0 43.03622697338457,44.7111647564733,0 42.98273333893267,44.72429025542684,0 42.94711324326666,44.73727121278075,0 42.91139377096744,44.74387478102076,0 42.86678779842207,44.75689566297324,0 42.82222718981076,44.77627663268911,0 42.78646057264498,44.78285467849167,0 42.75068361087877,44.78942153624351,0 42.71489664982047,44.79597749357526,0 42.67018293714294,44.8089434503947,0 42.63436265526764,44.81549043973975,0 42.58062882971889,44.82847708911186,0 42.53577121010387,44.82865760553419,0 42.4999208693445,44.83515250024195,0 42.46405955597668,44.84163606739416,0 42.41921437347892,44.84813226946913,0 42.34743402687941,44.8546589311031,0 42.28461265904593,44.86112695267551,0 42.23075235677478,44.8675535271605,0 42.14989565225897,44.87399084137638,0 42.11390246653278,44.88676612195948,0 42.05993854846654,44.89314814654652,0 42.02396195908283,44.89950167231509,0 41.9789497694081,44.9122091851487,0 41.93395441207461,44.91852527096788,0 41.8709082213281,44.93116423274844,0 41.83487300285083,44.93746204450073,0 41.78979408028204,44.94374575060081,0 41.73564639729818,44.95001729713729,0 41.69040551164322,44.96267366923298,0 41.64521725871576,44.96893289651593,0 41.60000973222633,44.975175757325,0 41.53672612986542,44.98133438865409,0 41.48244250948969,44.98749676965726,0 41.41904790587971,44.99362631059221,0 41.36465478024421,44.9997667282959,0 41.30118394243455,45.0058328814192,0 41.21035696372313,45.01810001242023,0 41.16475687358584,45.03060404802721,0 41.08305809003318,45.03643156262545,0 41.01022120757838,45.04866108931765,0 40.95564788310254,45.05460618921672,0 40.91918811076351,45.06068938106755,0 40.85529919457088,45.07290925496478,0 40.77294208672041,45.09133585558963,0 40.69975096549803,45.10342659498956,0 40.67223599748748,45.10954762666954,0 40.60826715231116,45.11528463578665,0 40.57135928605479,45.12770045512377,0 40.52555110464314,45.1335962585174,0 40.47035151607203,45.14577591815908,0 40.43361189969977,45.15174699931359,0 40.38743867936167,45.16402180769061,0 40.31380771819658,45.17592665584618,0 40.25839825627587,45.18803032898685,0 40.20297601134237,45.20010650582828,0 40.13835718316099,45.21202917610439,0 40.07397839683421,45.21751425339473,0 40.00883672469774,45.23582880740994,0 39.93507023862615,45.24112145045733,0 39.87931155020412,45.2530948415529,0 39.81464896123029,45.25847075344878,0 39.73120153969697,45.26992195087966,0 39.6663551131453,45.27525219860404,0 39.60108916496383,45.28696596304335,0 39.54499678429895,45.29882416872194,0 39.48923652215399,45.30422816521241,0 39.45191407720477,45.30996135331282,0 39.33011463236952,45.33337217162322,0 39.28379180440938,45.33245751303431,0 39.21765428078088,45.35046296264309,0 39.15227166561398,45.3555619831826,0 39.10540939634441,45.3610267026658,0 39.0301811039084,45.37231545305005,0 38.93615795673987,45.38317755359031,0 38.87977490498771,45.38837104664595,0 38.8611362654265,45.38794485212644,0 38.76739945004297,45.39222452308083,0 38.70209151973316,45.39065440109443,0 38.66422219910767,45.39620787040619,0 38.57948224848323,45.40060912002444,0 38.53212686453689,45.40590283250339,0 38.47592254071214,45.40447123138531,0 38.41969247627424,45.40301206561257,0 38.37281475292825,45.40177431165958,0 38.31652805906793,45.4002684092433,0 38.26953977469677,45.39899860039247,0 38.2225204175456,45.39771723895213,0 38.17548244051565,45.39641595431553,0 38.13783892001045,45.395360610056,0 38.09015720206296,45.40050051490334,0 38.04306020447405,45.39914471108719,0 37.94877077629969,45.39637607763545,0 37.88266036855689,45.39440863283985,0 37.8265581564524,45.386217910841,0 37.77043567566488,45.37799535308437,0 37.75150814308252,45.37740831723679,0 37.68523701121197,45.37532863532221,0 37.62907056001207,45.3670276412096,0 37.58167406687781,45.36547984955829,0 37.56272067532693,45.36485974724874,0 37.49624372816054,45.36270913594099,0 37.42976711206789,45.3604558515046,0 37.36300988528929,45.35826594092745,0 37.29631903836574,45.35598736154873,0 37.2574563269086,45.36116116878497,0 37.20010906702014,45.3592220898275,0 37.13254208285088,45.36290330815135,0 37.0655525681113,45.36091256593174,0 36.99876905778555,45.35842937608302,0 36.93202970910508,45.35589845692478,0 36.87419344573569,45.35378013197294,0 36.84528518883048,45.35271304946658,0 36.7985371039233,45.33791738963414,0 36.77194931640519,45.31731416288704,0 36.74544608511216,45.29666640798376,0 36.71978022459398,45.26949861492521,0 36.70380156937625,45.24271188567592,0 36.6877688993328,45.21591864758674,0 36.68220030795592,45.18298920764466,0 36.67644135182661,45.1501054833135,0 36.68108172872844,45.11111815847261,0 36.68413932080762,45.08519148093472,0 36.6982416402701,45.04652422941314,0 36.71895425088757,45.03431199144838,0 36.76176466252009,44.99690251128398,0 36.78237252013942,44.98469651020781,0 36.81400605981582,44.9598997677145,0 36.85432758358725,44.94198483400054,0 36.89463171023441,44.92406228333582,0 36.95607078108547,44.88744866471525,0 37.0056915338367,44.86989329050147,0 37.05590681577546,44.84586084391723,0 37.13577795837795,44.81003106420523,0 37.1661492932199,44.79170052673156,0 37.2840283303223,44.7507927272321,0 37.333699169088,44.72674043281787,0 37.39266858152133,44.70306913557176,0 37.44156188721184,44.68544143936163,0 37.48966540451932,44.67428275061241,0 37.52834674166351,44.66276488989296,0 37.57756480521245,44.63867678850659,0 37.6167789701361,44.6206512126293,0 37.64658130793807,44.60237958368824,0 37.68563249181236,44.58439603164166,0 37.72350647070319,44.57929352624546,0 37.76135764103405,44.57418080106547,0 37.79971359709912,44.56261018810848,0 37.83749478428704,44.55749069061759,0 37.86596991602401,44.55202519279939,0 37.91353842880665,44.5407661107667,0 37.95069554428976,44.54202127928167,0 37.96901833162684,44.54908041138402,0 38.01314831290035,44.57640274670221,0 + + + + + + 1 + + + + 41.53775554078113,41.54284169087985,0 41.5557362933533,41.54308773135198,0 41.59168899881018,41.54349741438154,0 41.62753474418238,41.55053498182795,0 41.67243157856005,41.55100515184548,0 41.70823459948813,41.55800681789061,0 41.7440345191207,41.56499465604114,0 41.7796477946567,41.58523610619877,0 41.81540029084289,41.59885565759765,0 41.85100111634027,41.62573535054879,0 41.87778979736473,41.63924597870949,0 41.90444289204574,41.66602302187243,0 41.92206151548901,41.6993597342634,0 42.04693067641833,41.80646201228668,0 42.13604393961469,41.89982122942289,0 42.17183308027819,41.92652169606252,0 42.19852871789745,41.95977352212215,0 42.2252628968215,41.97970842725169,0 42.26994872187819,41.99965020443318,0 42.29673577361682,42.01957606699964,0 42.33254608454833,42.03285823420884,0 42.35044607253829,42.04613765715574,0 42.38629271712682,42.06606236809689,0 42.4311496768144,42.0859710512487,0 42.46706331372805,42.09922911957894,0 42.502993728219,42.11247688800623,0 42.53894283546896,42.12571437916819,0 42.57489926553479,42.13894623322069,0 42.61985198507322,42.14548637159785,0 42.6648140035679,42.15200897086535,0 42.70975920132265,42.15186531039845,0 42.7457140791612,42.15173772830666,0 42.79065500174401,42.15156200799349,0 42.81761723751103,42.15144834777791,0 42.87154381883792,42.15120691211129,0 42.88948251478816,42.14449105785713,0 42.93431916747353,42.13759144636472,0 42.99713969807707,42.13060671855428,0 43.06883659142119,42.1168946332286,0 43.09568772190185,42.11008092547894,0 43.1315020603319,42.10320307300102,0 43.17627327311938,42.09625352467222,0 43.22989271585855,42.08257743750935,0 43.28347321227141,42.06885444601161,0 43.32817202801907,42.06183494226872,0 43.3818131650025,42.05471755909613,0 43.40868719679401,42.05446991900791,0 43.44464426678762,42.06077093451521,0 43.48983383300899,42.08025470959098,0 43.53518971128506,42.1063651468701,0 43.57162117444575,42.13255664457429,0 43.60840652610747,42.17201492544496,0 43.63593726230538,42.19829858984674,0 43.64561976620663,42.23147075519826,0 43.64631468213079,42.26474898795221,0 43.64700960042722,42.29802384606256,0 43.62941954714505,42.31820153960343,0 43.59380776363254,42.33858319170501,0 43.54024893401661,42.35912253707791,0 43.52238473691688,42.36596393345501,0 43.46863720819255,42.37981940818332,0 43.44162786499893,42.38008338950666,0 43.3697973887418,42.3940728486736,0 43.33373894928955,42.39440171561582,0 43.27985652940934,42.40818858744005,0 43.2437832018161,42.4084895392751,0 43.18985791220844,42.42223429039895,0 43.15376662807179,42.42250590127029,0 43.08165249776953,42.4296721637964,0 43.0365970877093,42.43662902324724,0 42.97339409950316,42.43701594915608,0 42.92837507808357,42.45059311942708,0 42.87418834415907,42.45087682837404,0 42.82912205079744,42.45775792484824,0 42.79307650237364,42.46458182687464,0 42.74800211233436,42.47142959219867,0 42.72101715155875,42.48485361330731,0 42.65795502138735,42.50504793003268,0 42.62188306119439,42.51181337142309,0 42.57680918934006,42.52524805411103,0 42.53171552103682,42.5453236739107,0 42.4773939052392,42.56541494626055,0 42.45020055709857,42.57212405074564,0 42.39581384265429,42.59884830001353,0 42.35949810336288,42.60555296409036,0 42.29590990712439,42.62559176168327,0 42.25954382216485,42.63893498303896,0 42.20494699261081,42.65893454593285,0 42.16856108827348,42.67225181411676,0 42.12310928360301,42.68553934625314,0 42.06853937910092,42.69879588322272,0 42.02301234779619,42.71871322359522,0 41.98658482768704,42.73197012891247,0 41.93193663650399,42.74516556255131,0 41.8863275391389,42.76503248686282,0 41.82242507396163,42.79149171599865,0 41.79500299216333,42.80472444625348,0 41.73988234604107,42.83122223678754,0 41.69368796281208,42.85778621032522,0 41.66597438741361,42.87105054289982,0 41.62905250953446,42.88427960424815,0 41.57367590476706,42.90409614079181,0 41.52737897401412,42.93060790088431,0 41.49030785595106,42.9504710178689,0 41.43516448825834,42.97014538786008,0 41.39841148287921,42.98323536689215,0 41.33370461438196,43.02283575676092,0 41.29674821910837,43.03593609563478,0 41.25970706568651,43.05569418075022,0 41.22270411169512,43.0687753648313,0 41.18567865624801,43.08184718692608,0 41.14855821584256,43.10157709004786,0 41.08354843051588,43.13441412574268,0 41.05568777197013,43.14088102660241,0 40.99972072685822,43.16714104809289,0 40.97161520577396,43.18693500869321,0 40.93430640248746,43.19997851502518,0 40.90615392097179,43.21976308849529,0 40.87808475586904,43.23287166021948,0 40.81244462881681,43.25903898424126,0 40.74653268526342,43.29187236784487,0 40.72758076919474,43.30507544992877,0 40.67100707738557,43.33129576545086,0 40.63336888345545,43.34430487580448,0 40.59551776613485,43.36400131053726,0 40.56697491011295,43.38379690364339,0 40.52929238947076,43.39677187567171,0 40.49158229972748,43.40973842205175,0 40.46318575886772,43.42280613228139,0 40.42520135412486,43.44246253179541,0 40.35851841607079,43.46859967230878,0 40.2920545002291,43.48798320432996,0 40.29186693076454,43.49467606366818,0 40.22485660673597,43.51417407539959,0 40.21481235678878,43.52753350534147,0 40.15672909129018,43.55389118804134,0 40.09883298603469,43.57351259881662,0 40.06004175537093,43.59325592294303,0 40.00245439033321,43.61935254888024,0 39.9833242227229,43.62580479212573,0 39.94476341368929,43.64542880184169,0 39.89700122671705,43.65817839613955,0 39.86838204498354,43.66447582070867,0 39.83029802350944,43.67062102850877,0 39.77298359656623,43.68318431178038,0 39.71561588042994,43.69569978988519,0 39.67706529680439,43.70185169199368,0 39.6295382941978,43.70122550352337,0 39.59236697914641,43.68042623868242,0 39.56605357924803,43.64606219379195,0 39.5680943256674,43.61227393564905,0 39.56933450184233,43.59198717577144,0 39.57138689975699,43.55816983965688,0 39.59225023718366,43.52475242713699,0 39.62168558290202,43.50502984680832,0 39.66091151318264,43.47866256184555,0 39.70942807994742,43.45262153219434,0 39.75862473346064,43.41300280243701,0 39.78785020872552,43.39328905300923,0 39.82677471399703,43.36698906613079,0 39.84693702219752,43.34037904684922,0 39.89543417295292,43.30753036807191,0 39.96285281113042,43.26827568651616,0 40.00117377725787,43.24872331473418,0 40.03012486622227,43.22901364276324,0 40.05871639761818,43.21602938254538,0 40.11612240784784,43.18332213420401,0 40.1353343850455,43.17018093074569,0 40.19259980331036,43.13747560172189,0 40.24978348352383,43.10478282219141,0 40.25934837429926,43.09820972028794,0 40.3160800643051,43.07219370716132,0 40.3539478839585,43.05260526031623,0 40.41052991283914,43.02659535458304,0 40.46678080177634,43.00727222906239,0 40.49500740972938,42.99423189364647,0 40.5419270147596,42.97474033208791,0 40.60742162080103,42.94880418863689,0 40.67279963510654,42.9228378057344,0 40.72912837383883,42.89003553322812,0 40.76657456827925,42.8703943730393,0 40.81293104737578,42.85755905855336,0 40.85044054112145,42.8312065939578,0 40.87836129697151,42.8181357379184,0 40.93396511184284,42.7986735566373,0 40.97118396529061,42.77899668912436,0 41.01770393338185,42.75271927536402,0 41.07330108293286,42.72651965302641,0 41.10123341197125,42.70673087941755,0 41.12913879068348,42.68694676806709,0 41.15717452916644,42.66046729786308,0 41.21266587733444,42.62752013054661,0 41.27753680000098,42.58133347938784,0 41.31449162942334,42.55492161154923,0 41.34218069105316,42.53517337956085,0 41.3699521159656,42.50864535973016,0 41.3977337373972,42.48216315570383,0 41.42580420794186,42.43564706263653,0 41.44442509268509,42.40910366742161,0 41.46330987180127,42.36252566285938,0 41.47300777847832,42.3225579626207,0 41.49205605734203,42.26266839510883,0 41.51091812899023,42.20945509965943,0 41.51124736400526,42.18940417181055,0 41.51184899375748,42.14940704737933,0 41.51257301985768,42.09604680192994,0 41.51304361340566,42.06270781873246,0 41.51351460595959,42.02937155010276,0 41.51389103853685,42.00270439552118,0 41.51426753630366,41.97603845148638,0 41.51454886823195,41.9560408205822,0 41.51529969022265,41.90271680024225,0 41.51601958509476,41.84939179639044,0 41.50738364776096,41.82268764844807,0 41.50794314458717,41.78265830783323,0 41.49956653289131,41.73593204200335,0 41.49094379732394,41.7091836430297,0 41.48241637344626,41.67576839642926,0 41.48277005855586,41.64910177468716,0 41.46538962600514,41.60894168315224,0 41.46583961205656,41.57560963453324,0 41.53775554078113,41.54284169087985,0 + + + + + + 1 + + + + 46.29136056526277,41.70642736508859,0 46.2655374598496,41.72746512034644,0 46.24798959676117,41.73478228033488,0 46.19564432800482,41.76341874451683,0 46.16115209622109,41.79143028820085,0 46.11736962289969,41.81302249013741,0 46.08220077071437,41.82760279762712,0 46.07393530816669,41.84128122420302,0 46.02953907168371,41.86296653864726,0 45.97592576115243,41.88496605089954,0 45.96712042768141,41.89198115153647,0 45.91376114310117,41.920632764118,0 45.86013833031562,41.94254110596773,0 45.80676845790597,41.97110399371204,0 45.77101722026075,41.98565920730643,0 45.725955564429,41.99379516697699,0 45.68099840062693,42.00862816602489,0 45.63565270913485,42.01677973576709,0 45.5812152974966,42.0251941414858,0 45.52690343264386,42.02683115346582,0 45.48201492865787,42.02137122038625,0 45.45493178565337,42.01542542452609,0 45.40967572857903,42.00328169139971,0 45.35552095218777,41.99134464290105,0 45.31840845667836,41.97238166781943,0 45.29099863128936,41.96648146570077,0 45.23621071348215,41.95466470778074,0 45.19969202127459,41.94228297108083,0 45.15423561361719,41.93011569448655,0 45.1090988089524,41.92458878469146,0 45.05471454575017,41.91260804562862,0 45.0006433083413,41.90725980387611,0 44.95588715117321,41.90832075394844,0 44.90190046564182,41.90290500703968,0 44.84792260312036,41.89746351754019,0 44.80317141083541,41.8984582160928,0 44.75815683274477,41.89277719190957,0 44.71340818825777,41.89373744246018,0 44.6860244148217,41.8809885369469,0 44.63145468005109,41.8621918212395,0 44.59502689459989,41.84968114723631,0 44.55862312027542,41.83716212732757,0 44.51302072169235,41.81817931718435,0 44.48518326606419,41.79213691670284,0 44.45715170864211,41.75943595332741,0 44.44729881207198,41.73300160978207,0 44.42827616640895,41.70010583641513,0 44.41822805812167,41.66701663104404,0 44.40819056272188,41.63392764776611,0 44.4073228431249,41.60729951896295,0 44.40578327122952,41.56072419136571,0 44.41337823615215,41.52061272268416,0 44.43879679781359,41.48011372054618,0 44.48146939519803,41.42597045923384,0 44.49856796833567,41.40563622444879,0 44.54189197339844,41.37139214028922,0 44.59382879531736,41.33027103698075,0 44.65486748320358,41.29554587105938,0 44.71607012701851,41.26747049424475,0 44.75945136971854,41.23980950145585,0 44.80386201218432,41.23873212776564,0 44.83939508710957,41.23785862285534,0 44.89270076787832,41.23652617709271,0 44.93772737194195,41.24867818391856,0 44.97328205520126,41.24776308881047,0 45.01907030278738,41.27309403085943,0 45.07369949925368,41.29141017020593,0 45.10983278395497,41.29696806464043,0 45.15527303267147,41.30888709188234,0 45.19128061237639,41.31453219506862,0 45.23602909997862,41.32002510612885,0 45.29857768985443,41.3250143950737,0 45.33416190651862,41.32403215347762,0 45.39671697915616,41.32213231990524,0 45.45940221481742,41.32014120641703,0 45.50419250763679,41.31869709118127,0 45.54003268838061,41.31753670697918,0 45.57551084597447,41.30970076629319,0 45.62888963209738,41.30126446312586,0 45.68227301560751,41.2928005994364,0 45.72667875602514,41.2846220366463,0 45.78000434070471,41.27613899870758,0 45.83298573021339,41.26094177619265,0 45.85052958090154,41.25364113518242,0 45.91244252847301,41.23809291562738,0 45.98361453558361,41.22889643865703,0 46.01900165480877,41.22094340493064,0 46.08099732081731,41.20524529321233,0 46.12596191925329,41.20349831104546,0 46.16194497038691,41.20208815960403,0 46.23318663834206,41.1858368946756,0 46.26918939591037,41.18438942888334,0 46.35924251833756,41.18071667516919,0 46.41346204771126,41.17841788224271,0 46.44127361113204,41.19070266410098,0 46.46052644083118,41.21004447149864,0 46.48921841312685,41.23570883496545,0 46.49992571005606,41.26211184915207,0 46.51106905429771,41.29520785111438,0 46.52093812397818,41.30822447349239,0 46.52351243433359,41.34838352045616,0 46.5252309386499,41.37515650858474,0 46.52738102506081,41.40862114563205,0 46.52953279269449,41.44208467456748,0 46.53168678136583,41.47554737894669,0 46.52450690141258,41.50946657468598,0 46.4984229782033,41.53070656409578,0 46.47267856199456,41.55864564162263,0 46.45527834611592,41.57278820142519,0 46.4117624801078,41.60811801287936,0 46.37733789777619,41.63623784726956,0 46.34296583161303,41.66432492867152,0 46.3353640999735,41.69146384505699,0 46.29136056526277,41.70642736508859,0 + + + + + + + + diff --git a/scripts/python/map_generator/configs/Caucasus/MediumResolution.yml b/scripts/python/map_generator/configs/Caucasus/MediumResolution.yml new file mode 100644 index 00000000..d8ab84d4 --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/MediumResolution.yml @@ -0,0 +1,5 @@ +{ + 'output_directory': '.\Caucasus', # Where to save the output files + 'boundary_file': '.\configs\Caucasus\MediumResolution.kml', # Input kml file setting the boundary of the map to create + 'zoom_factor': 0.25 # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] +} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/Caucasus/airbases.json b/scripts/python/map_generator/configs/Caucasus/airbases.json new file mode 100644 index 00000000..12626ffd --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/airbases.json @@ -0,0 +1 @@ +{"airbases":{"1":{"callsign":"Anapa-Vityazevo","coalition":"neutral","latitude":45.013174733771677,"longitude":37.359783477555922},"10":{"callsign":"Gudauta","coalition":"neutral","latitude":43.124233340197144,"longitude":40.564175768400638},"11":{"callsign":"Batumi","coalition":"neutral","latitude":41.603279859649049,"longitude":41.609275483509791},"12":{"callsign":"Senaki-Kolkhi","coalition":"neutral","latitude":42.238728081573278,"longitude":42.061021312855914},"13":{"callsign":"Kobuleti","coalition":"neutral","latitude":41.93210535345338,"longitude":41.876483823101026},"14":{"callsign":"Kutaisi","coalition":"neutral","latitude":42.179153937689627,"longitude":42.495684077400142},"15":{"callsign":"Mineralnye Vody","coalition":"neutral","latitude":44.218646823806807,"longitude":43.100679733081456},"16":{"callsign":"Nalchik","coalition":"neutral","latitude":43.510071438529849,"longitude":43.625108736097914},"17":{"callsign":"Mozdok","coalition":"neutral","latitude":43.791303250938249,"longitude":44.620327262102009},"18":{"callsign":"Tbilisi-Lochini","coalition":"neutral","latitude":41.674720064437075,"longitude":44.946875226153338},"19":{"callsign":"Soganlug","coalition":"neutral","latitude":41.641163266786613,"longitude":44.947183065316693},"2":{"callsign":"Krasnodar-Center","coalition":"neutral","latitude":45.087429883845076,"longitude":38.925202300775062},"20":{"callsign":"Vaziani","coalition":"neutral","latitude":41.637735936261556,"longitude":45.019090938460067},"21":{"callsign":"Beslan","coalition":"neutral","latitude":43.208500987380937,"longitude":44.588922553542936},"3":{"callsign":"Novorossiysk","coalition":"neutral","latitude":44.673329604126899,"longitude":37.786226060479564},"4":{"callsign":"Krymsk","coalition":"neutral","latitude":44.961383022734175,"longitude":37.985886938697085},"5":{"callsign":"Maykop-Khanskaya","coalition":"neutral","latitude":44.67144025735508,"longitude":40.021427482235985},"6":{"callsign":"Gelendzhik","coalition":"neutral","latitude":44.56767458600406,"longitude":38.004146350528103},"7":{"callsign":"Sochi-Adler","coalition":"neutral","latitude":43.439378434050852,"longitude":39.924231880466095},"8":{"callsign":"Krasnodar-Pashkovsky","coalition":"neutral","latitude":45.0460996415433,"longitude":39.203066906324537},"9":{"callsign":"Sukhumi-Babushara","coalition":"neutral","latitude":42.852741071634995,"longitude":41.142447588488196}},"frameRate":60,"load":0,"sessionHash":"K2n7kpGE9yOaYE4G","time":"1709136685634"} \ No newline at end of file diff --git a/scripts/python/map_generator/configs/Caucasus/airbases.kml b/scripts/python/map_generator/configs/Caucasus/airbases.kml new file mode 100644 index 00000000..983ec63d --- /dev/null +++ b/scripts/python/map_generator/configs/Caucasus/airbases.kml @@ -0,0 +1 @@ +doc namedoc description1namedescription137.232713,44.923343 37.232713,45.103006 37.486854,45.103006 37.486854,44.923343 37.232713,44.923343namedescription140.441098,43.034402 40.441098,43.214065 40.687254,43.214065 40.687254,43.034402 40.441098,43.034402namedescription141.489141,41.513448 41.489141,41.693111 41.729410,41.693111 41.729410,41.513448 41.489141,41.513448namedescription141.939685,42.148897 41.939685,42.328560 42.182358,42.328560 42.182358,42.148897 41.939685,42.148897namedescription141.755732,41.842274 41.755732,42.021937 41.997235,42.021937 41.997235,41.842274 41.755732,41.842274namedescription142.374462,42.089322 42.374462,42.268985 42.616906,42.268985 42.616906,42.089322 42.374462,42.089322namedescription142.975336,44.128815 42.975336,44.308478 43.226023,44.308478 43.226023,44.128815 42.975336,44.128815namedescription143.501246,43.420240 43.501246,43.599903 43.748971,43.599903 43.748971,43.420240 43.501246,43.420240namedescription144.495884,43.701472 44.495884,43.881135 44.744771,43.881135 44.744771,43.701472 44.495884,43.701472namedescription144.826608,41.584889 44.826608,41.764552 45.067143,41.764552 45.067143,41.584889 44.826608,41.584889namedescription144.826978,41.551332 44.826978,41.730995 45.067388,41.730995 45.067388,41.551332 44.826978,41.551332namedescription138.797967,44.997598 38.797967,45.177261 39.052438,45.177261 39.052438,44.997598 38.797967,44.997598namedescription144.898893,41.547904 44.898893,41.727567 45.139289,41.727567 45.139289,41.547904 44.898893,41.547904namedescription144.465674,43.118669 44.465674,43.298333 44.712171,43.298333 44.712171,43.118669 44.465674,43.118669namedescription137.659903,44.583498 37.659903,44.763161 37.912549,44.763161 37.912549,44.583498 37.659903,44.583498namedescription137.858932,44.871551 37.858932,45.051215 38.112842,45.051215 38.112842,44.871551 37.858932,44.871551namedescription139.895109,44.581609 39.895109,44.761272 40.147746,44.761272 40.147746,44.581609 39.895109,44.581609namedescription137.878053,44.477843 37.878053,44.657506 38.130239,44.657506 38.130239,44.477843 37.878053,44.477843namedescription139.800514,43.349547 39.800514,43.529210 40.047949,43.529210 40.047949,43.349547 39.800514,43.349547namedescription139.075924,44.956268 39.075924,45.135931 39.330210,45.135931 39.330210,44.956268 39.075924,44.956268namedescription141.019912,42.762910 41.019912,42.942573 41.264983,42.942573 41.264983,42.762910 41.019912,42.762910 \ No newline at end of file diff --git a/scripts/python/map_generator/configs/LasVegas/config.yml b/scripts/python/map_generator/configs/LasVegas/config.yml deleted file mode 100644 index c7b54066..00000000 --- a/scripts/python/map_generator/configs/LasVegas/config.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - 'output_directory': '.\LasVegas', # Where to save the output files - 'boundary_file': '.\configs\LasVegas\boundary.kml', # Input kml file setting the boundary of the map to create - 'zoom_factor': 0.02, # [0: maximum zoom in (things look very big), 1: maximum zoom out (things look very small)] - 'geo_width': 1.14 -} \ No newline at end of file diff --git a/scripts/python/map_generator/main.py b/scripts/python/map_generator/main.py index baa269d8..40ce8907 100644 --- a/scripts/python/map_generator/main.py +++ b/scripts/python/map_generator/main.py @@ -2,7 +2,6 @@ import sys import yaml import json import requests -import time from pyproj import Geod from fastkml import kml @@ -11,6 +10,9 @@ from datetime import timedelta import map_generator +# Port on which the camera control module is listening +port = 3003 + if len(sys.argv) == 1: print("Please provide a configuration file as first argument. You can also drop the configuration file on this script to run it.") else: @@ -53,9 +55,12 @@ else: if 'geo_width' not in map_config: # Let the user input the size of the screen to compute resolution - data = json.dumps({'lat': features[0].geometry.bounds[1], 'lng': features[0].geometry.bounds[0], 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350)}) - r = requests.put('http://localhost:8080', data = data) - print("The F10 map in your DCS installation was setup. Please, use the measure tool and measure the width of the screen in Nautical Miles") + data = json.dumps({'lat': features[0].geometry.bounds[1], 'lng': features[0].geometry.bounds[0], 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350), 'mode': 'map'}) + try: + r = requests.put(f'http://127.0.0.1:{port}', data = data) + print("The F10 map in your DCS installation was setup. Please, use the measure tool and measure the width of the screen in Nautical Miles") + except: + print("No running DCS instance detected. You can still run the algorithm if you already took the screenshots, otherwise you will not be able to produce a map.") map_config['geo_width'] = input("Insert the width of the screen in Nautical Miles: ") map_config['mpps'] = float(map_config['geo_width']) * 1852 / screen_config['width'] @@ -72,7 +77,7 @@ else: print(f"Estimated time to complete: {timedelta(seconds=total_time * 0.15)} (hh:mm:ss)") input("Press enter to continue...") - map_generator.run(map_config) + map_generator.run(map_config, port) diff --git a/scripts/python/map_generator/map_generator.py b/scripts/python/map_generator/map_generator.py index 27726b27..78f914c7 100644 --- a/scripts/python/map_generator/map_generator.py +++ b/scripts/python/map_generator/map_generator.py @@ -5,6 +5,7 @@ import time import os import yaml import json +import numpy from fastkml import kml from shapely import wkt, Point @@ -19,7 +20,7 @@ tot_futs = 0 # constants C = 40075016.686 # meters, Earth equatorial circumference -R = C / (2 * math.pi) +R = C / (2 * math.pi) # meters, Earth equatorial radius def deg_to_num(lat_deg, lon_deg, zoom): lat_rad = math.radians(lat_deg) @@ -60,9 +61,9 @@ def extract_tiles(n, screenshots_XY, params): n_height = params['n_height'] XY = screenshots_XY[n] - if (os.path.exists(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg"))): + if (os.path.exists(os.path.join(output_directory, "screenshots", f"{f}_{n}_{zoom}.jpg"))): # Open the source screenshot - img = Image.open(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg")) + img = Image.open(os.path.join(output_directory, "screenshots", f"{f}_{n}_{zoom}.jpg")) # Compute the Web Mercator Projection position of the top left corner of the most centered tile X_center, Y_center = XY[0], XY[1] @@ -84,6 +85,7 @@ def extract_tiles(n, screenshots_XY, params): try: os.mkdir(os.path.join(output_directory, "tiles", str(zoom), str(X))) except FileExistsError: + # Ignore this error, it means one other thread has already created the folder continue except Exception as e: raise e @@ -91,33 +93,53 @@ def extract_tiles(n, screenshots_XY, params): n += 1 else: - raise Exception(f"{os.path.join(output_directory, 'screenshots', f'{f}_{n}.jpg')} missing") + raise Exception(f"{os.path.join(output_directory, 'screenshots', f'{f}_{n}_{zoom}.jpg')} missing") def merge_tiles(base_path, zoom, tile): X = tile[0] Y = tile[1] - positions = [(0, 0), (0, 1), (1, 0), (1, 1)] + # If the image already exists, open it so we can paste the higher quality data in it + if os.path.exists(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg")): + dst = Image.open(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg")) + dst = make_background_transparent(dst) + else: + dst = Image.new('RGB', (256, 256), (221, 221, 221)) - dst = Image.new('RGB', (256, 256), (0, 0, 0, 0)) + # Loop on all the 4 subtiles in the tile + positions = [(0, 0), (0, 1), (1, 0), (1, 1)] for i in range(0, 4): + # Open the subtile, if it exists, and resize it down to 128x128 if os.path.exists(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")): im = Image.open(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")).resize((128, 128)) - else: - im = Image.new('RGB', (128, 128), (0, 0, 0, 0)) - dst.paste(im, (positions[i][0] * 128, positions[i][1] * 128)) + im = make_background_transparent(im) + dst.paste(im, (positions[i][0] * 128, positions[i][1] * 128)) + # Create the output folder if it exists if not os.path.exists(os.path.join(base_path, str(zoom - 1), str(X))): try: os.mkdir(os.path.join(base_path, str(zoom - 1), str(X))) except FileExistsError: + # Ignore this error, it means one other thread has already created the folder pass except Exception as e: raise e - - dst.save(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg"), quality=95) -def run(map_config): + # Save the image + dst.convert('RGB').save(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg"), quality=95) + +def make_background_transparent(im): + im.putalpha(255) + data = numpy.array(im) + red, green, blue, alpha = data.T + + # If present, remove any "background" areas + background_areas = (red == 221) & (blue == 221) & (green == 221) + data[..., :][background_areas.T] = (0, 0, 0, 0) # make transparent + + return Image.fromarray(data) + +def run(map_config, port): global tot_futs, fut_counter with open('configs/screen_properties.yml', 'r') as sp: @@ -203,8 +225,8 @@ def run(map_config): # Making PUT request # If the number of rows or columns is odd, we need to take the picture at the CENTER of the tile! lat, lng = num_to_deg(XY[0] + (n_width % 2) / 2, XY[1] + (n_height % 2) / 2, zoom) - data = json.dumps({'lat': lat, 'lng': lng, 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350)}) - r = requests.put('http://localhost:8080', data = data) + data = json.dumps({'lat': lat, 'lng': lng, 'alt': 1350 + map_config['zoom_factor'] * (25000 - 1350), 'mode': 'map'}) + r = requests.put(f'http://127.0.0.1:{port}', data = data) geo_data = json.loads(r.text) @@ -233,7 +255,7 @@ def run(map_config): sy = s_height / m_height # Resize, rotate and save the screenshot - screenshot.resize((int(sx * screenshot.width), int(sy * screenshot.height))).rotate(math.degrees(geo_data['northRotation'])).save(os.path.join(output_directory, "screenshots", f"{f}_{n}.jpg"), quality=95) + screenshot.resize((int(sx * screenshot.width), int(sy * screenshot.height))).rotate(math.degrees(geo_data['northRotation'])).save(os.path.join(output_directory, "screenshots", f"{f}_{n}_{zoom}.jpg"), quality=95) printProgressBar(n + 1, len(screenshots_XY)) n += 1