Spaces:
Running
Running
deploy: groups + search-point API
Browse files- backend/export_data.py +59 -0
- backend/export_positions.csv +335 -0
- backend/export_positions.json +1672 -0
- backend/export_streets.csv +104 -0
- backend/export_streets.json +414 -0
- backend/main.py +213 -4
- backend/models.py +28 -0
- backend/php_unserialize.py +143 -0
- backend/position.bin +3 -0
- backend/services/maps_service.py +564 -53
backend/export_data.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
from config import supabase
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def export_streets(output_path="export_streets.csv"):
|
| 10 |
+
all_data = []
|
| 11 |
+
page_size = 1000
|
| 12 |
+
offset = 0
|
| 13 |
+
while True:
|
| 14 |
+
result = (
|
| 15 |
+
supabase.table("Streets")
|
| 16 |
+
.select("id, name")
|
| 17 |
+
.order("id")
|
| 18 |
+
.range(offset, offset + page_size - 1)
|
| 19 |
+
.execute()
|
| 20 |
+
)
|
| 21 |
+
rows = result.data or []
|
| 22 |
+
all_data.extend(rows)
|
| 23 |
+
if len(rows) < page_size:
|
| 24 |
+
break
|
| 25 |
+
offset += page_size
|
| 26 |
+
with open(output_path, "w", encoding="utf-8", newline="") as f:
|
| 27 |
+
writer = csv.DictWriter(f, fieldnames=["id", "name"])
|
| 28 |
+
writer.writeheader()
|
| 29 |
+
writer.writerows(all_data)
|
| 30 |
+
print(f"Exported {len(all_data)} streets → {output_path}")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def export_positions(output_path="export_positions.csv"):
|
| 34 |
+
all_data = []
|
| 35 |
+
page_size = 1000
|
| 36 |
+
offset = 0
|
| 37 |
+
while True:
|
| 38 |
+
result = (
|
| 39 |
+
supabase.table("Positions")
|
| 40 |
+
.select("id, long, lat, streets")
|
| 41 |
+
.order("id")
|
| 42 |
+
.range(offset, offset + page_size - 1)
|
| 43 |
+
.execute()
|
| 44 |
+
)
|
| 45 |
+
rows = result.data or []
|
| 46 |
+
all_data.extend(rows)
|
| 47 |
+
if len(rows) < page_size:
|
| 48 |
+
break
|
| 49 |
+
offset += page_size
|
| 50 |
+
with open(output_path, "w", encoding="utf-8", newline="") as f:
|
| 51 |
+
writer = csv.DictWriter(f, fieldnames=["id", "long", "lat", "streets"])
|
| 52 |
+
writer.writeheader()
|
| 53 |
+
writer.writerows(all_data)
|
| 54 |
+
print(f"Exported {len(all_data)} positions → {output_path}")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
export_streets()
|
| 59 |
+
export_positions()
|
backend/export_positions.csv
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
id,long,lat,streets
|
| 2 |
+
1,106.636137946531,10.8027874729889,"[52, 21]"
|
| 3 |
+
2,106.635414956844,10.8025041096741,[52]
|
| 4 |
+
3,106.633984993906,10.8019373830444,[52]
|
| 5 |
+
4,106.631783891969,10.798442568828,"[52, 55, 58]"
|
| 6 |
+
5,106.629414092438,10.7947982050797,"[52, 59, 60]"
|
| 7 |
+
6,106.628401906875,10.7928382236824,"[52, 61]"
|
| 8 |
+
7,106.629815753375,10.799229689147,[55]
|
| 9 |
+
8,106.627502186375,10.8003631424064,[55]
|
| 10 |
+
9,106.633037074094,10.7976397061026,[58]
|
| 11 |
+
10,106.632386383375,10.7938851213766,"[58, 59]"
|
| 12 |
+
11,106.6308118725,10.7941212637136,[59]
|
| 13 |
+
12,106.634266156563,10.7935860077497,[59]
|
| 14 |
+
13,106.637849022125,10.7930035233183,"[59, 62]"
|
| 15 |
+
14,106.627935980188,10.7956325746706,[60]
|
| 16 |
+
15,106.626923794625,10.7956955459605,[60]
|
| 17 |
+
16,106.623822972188,10.7958057457178,"[57, 60]"
|
| 18 |
+
17,106.622714388,10.7957427744279,[60]
|
| 19 |
+
18,106.620143808,10.7956168318481,[60]
|
| 20 |
+
19,106.623967570125,10.7992060755374,[57]
|
| 21 |
+
20,106.62457809475,10.8013155579924,[57]
|
| 22 |
+
21,106.619035223813,10.7957112887829,[60]
|
| 23 |
+
22,106.616753789688,10.795915945475,[60]
|
| 24 |
+
23,106.61442415625,10.7950500902393,[60]
|
| 25 |
+
24,106.626931827844,10.7930979802531,[61]
|
| 26 |
+
25,106.625542081,10.7933105083564,[61]
|
| 27 |
+
26,106.628980298625,10.7927752523925,[61]
|
| 28 |
+
27,106.631117134812,10.7924446531207,[61]
|
| 29 |
+
28,106.633993027125,10.7920038540915,[61]
|
| 30 |
+
29,106.637591959125,10.7914528553051,"[61, 62]"
|
| 31 |
+
30,106.638290849156,10.7955066320908,"[62, 66]"
|
| 32 |
+
31,106.637358995781,10.7901225868065,"[62, 64]"
|
| 33 |
+
32,106.636748471156,10.7863835473994,"[62, 63]"
|
| 34 |
+
33,106.63601744825,10.7815897312587,"[62, 67]"
|
| 35 |
+
34,106.635864817094,10.7804168649098,[62]
|
| 36 |
+
35,106.634129591844,10.7765675667822,[62]
|
| 37 |
+
36,106.633519067219,10.7757646457004,[62]
|
| 38 |
+
37,106.631783891969,10.7701440998613,"[62, 77]"
|
| 39 |
+
38,106.632346217281,10.7639487869626,[62]
|
| 40 |
+
39,106.634209924031,10.7588239903606,[62]
|
| 41 |
+
40,106.637567859469,10.7545335352288,"[62, 33]"
|
| 42 |
+
41,106.633462834688,10.7876036483877,[63]
|
| 43 |
+
42,106.630394145125,10.787981486113,[63]
|
| 44 |
+
43,106.627646784313,10.7875091889563,[63]
|
| 45 |
+
44,106.62277865375,10.7884222967926,"[63, 102]"
|
| 46 |
+
45,106.633784163438,10.7907050712379,[64]
|
| 47 |
+
46,106.630956470438,10.7911773559119,[64]
|
| 48 |
+
47,106.629751487625,10.791413498249,[64]
|
| 49 |
+
48,106.643440142375,10.7866511824549,"[65, 68]"
|
| 50 |
+
49,106.646107171,10.7879106415395,[65]
|
| 51 |
+
50,106.641624634938,10.7858876353849,"[66, 67]"
|
| 52 |
+
51,106.643327677312,10.7813063755201,[66]
|
| 53 |
+
52,106.640194722,10.7900202584605,[66]
|
| 54 |
+
53,106.639311067937,10.7926807954577,[66]
|
| 55 |
+
54,106.644532660125,10.7795903285021,[66]
|
| 56 |
+
55,106.645994705937,10.7774334620666,[66]
|
| 57 |
+
56,106.648019077063,10.7747413149098,"[66, 69]"
|
| 58 |
+
57,106.640226854875,10.7848092235437,[67]
|
| 59 |
+
58,106.634185824375,10.780621531068,"[67, 101]"
|
| 60 |
+
59,106.628610770563,10.7775357951456,[67]
|
| 61 |
+
60,106.621445189438,10.7735999005548,"[67, 77]"
|
| 62 |
+
61,106.642042362312,10.7848800681172,"[68, 66]"
|
| 63 |
+
62,106.6436650725,10.7854153382282,[68]
|
| 64 |
+
63,106.643070614313,10.7880759455444,[68]
|
| 65 |
+
64,106.642877817062,10.7893197028606,[68]
|
| 66 |
+
65,106.645834041562,10.7968604569868,"[68, 21]"
|
| 67 |
+
66,106.646099137781,10.7720255038835,[69]
|
| 68 |
+
67,106.644492494031,10.7697583726768,[69]
|
| 69 |
+
68,106.642355657844,10.7667512750347,"[69, 76, 77]"
|
| 70 |
+
69,106.649762235531,10.7771107192788,[69]
|
| 71 |
+
70,106.649890767031,10.7794250212205,[69]
|
| 72 |
+
71,106.650163896469,10.7821092581311,[69]
|
| 73 |
+
72,106.651015417656,10.7845966898232,"[69, 65]"
|
| 74 |
+
73,106.652450150525,10.7901619438627,"[69, 47]"
|
| 75 |
+
74,106.642259259219,10.7628466801664,"[69, 74]"
|
| 76 |
+
75,106.641568402406,10.7589263288488,[69]
|
| 77 |
+
76,106.637599992344,10.7586271829404,[69]
|
| 78 |
+
79,106.635390857188,10.7575880262136,"[69, 62, 103]"
|
| 79 |
+
80,106.629598856469,10.8231887713592,"[21, 70]"
|
| 80 |
+
81,106.630675307781,10.8204183092233,[21]
|
| 81 |
+
82,106.632820177188,10.813641559414,[21]
|
| 82 |
+
83,106.634651751063,10.807533768568,"[21, 22]"
|
| 83 |
+
84,106.635776451688,10.8037713733877,[21]
|
| 84 |
+
85,106.63666010575,10.8018586710125,"[21, 66]"
|
| 85 |
+
86,106.641174774687,10.7993713708044,[21]
|
| 86 |
+
87,106.644050667,10.7978443573856,[21]
|
| 87 |
+
88,106.646926559313,10.7962701589806,"[21, 72]"
|
| 88 |
+
89,106.648501020187,10.7954200465673,[21]
|
| 89 |
+
90,106.650163896469,10.794569934154,[21]
|
| 90 |
+
91,106.653361117531,10.7928067380374,"[21, 47, 23, 46]"
|
| 91 |
+
92,106.630803839281,10.8255342194175,[70]
|
| 92 |
+
93,106.640122423031,10.844297018724,[70]
|
| 93 |
+
94,106.634916847281,10.8344593117198,[70]
|
| 94 |
+
95,106.635913016406,10.8364426058946,[70]
|
| 95 |
+
96,106.636049581125,10.8062744093447,"[22, 73]"
|
| 96 |
+
97,106.637591959125,10.8041649826456,[22]
|
| 97 |
+
98,106.638732676188,10.8028110865985,[22]
|
| 98 |
+
99,106.641640701375,10.8023230720007,[22]
|
| 99 |
+
100,106.64745675175,10.8018665422157,"[22, 72]"
|
| 100 |
+
101,106.654509867813,10.8012683307732,[22]
|
| 101 |
+
102,106.661293167725,10.800564645208,"[22, 23]"
|
| 102 |
+
103,106.640106356594,10.7931452087205,[71]
|
| 103 |
+
104,106.640829346281,10.7955617319695,[71]
|
| 104 |
+
105,106.641953996906,10.7989620682386,"[71, 21]"
|
| 105 |
+
106,106.647223788406,10.7994185980236,[72]
|
| 106 |
+
107,106.648107442469,10.8093991944175,"[72, 73]"
|
| 107 |
+
108,106.644862022094,10.8096746792476,[73]
|
| 108 |
+
109,106.640210788437,10.8078249953883,[73]
|
| 109 |
+
110,106.655048093469,10.8086435788835,[73]
|
| 110 |
+
111,106.652196300813,10.7688609665742,"[66, 74, 78, 79]"
|
| 111 |
+
112,106.646396366875,10.7652240819695,"[74, 75, 76]"
|
| 112 |
+
113,106.651151982375,10.7626971085298,[75]
|
| 113 |
+
114,106.6570322985,10.7621460551318,"[75, 78, 89]"
|
| 114 |
+
115,106.644853988875,10.7658144963245,[76]
|
| 115 |
+
116,106.637583925906,10.7689081984743,[77]
|
| 116 |
+
117,106.63563185375,10.7694120054092,[77]
|
| 117 |
+
118,106.644942354281,10.7642243136616,"[74, 96]"
|
| 118 |
+
119,106.654558067125,10.7656727968793,[78]
|
| 119 |
+
120,106.652758626125,10.7711359697642,"[79, 80, 81]"
|
| 120 |
+
121,106.653272752125,10.7755757230929,[80]
|
| 121 |
+
122,106.655907647875,10.7783465880028,"[80, 47]"
|
| 122 |
+
123,106.65812481625,10.7702543076283,"[81, 82, 47]"
|
| 123 |
+
124,106.663606734725,10.7763896646602,"[82, 83]"
|
| 124 |
+
125,106.672054467562,10.7828649335818,"[82, 46]"
|
| 125 |
+
126,106.661418485938,10.7734975647712,[82]
|
| 126 |
+
127,106.664789224525,10.7733416995007,[83]
|
| 127 |
+
128,106.658851019225,10.7807348846325,"[83, 84]"
|
| 128 |
+
129,106.664596427275,10.7868558445562,"[84, 46]"
|
| 129 |
+
130,106.661794440575,10.7837449806172,[84]
|
| 130 |
+
131,106.656087591975,10.7778128816366,"[84, 47]"
|
| 131 |
+
132,106.665573266675,10.7778506661581,"[85, 82]"
|
| 132 |
+
133,106.668015365175,10.7746137921498,[85]
|
| 133 |
+
134,106.670769152562,10.769637668932,"[85, 89]"
|
| 134 |
+
135,106.671101192271,10.7698161005548,"[85, 89]"
|
| 135 |
+
136,106.671551052521,10.7677903768377,"[85, 28]"
|
| 136 |
+
137,106.671818826479,10.7666463152566,[85]
|
| 137 |
+
138,106.672268686729,10.7646100510402,"[85, 92]"
|
| 138 |
+
139,106.672675703146,10.76277320638,[85]
|
| 139 |
+
140,106.673146985312,10.7607474291262,"[85, 36]"
|
| 140 |
+
141,106.673296938729,10.7600756687933,"[85, 93]"
|
| 141 |
+
142,106.674089523792,10.7575040539528,"[85, 31, 94]"
|
| 142 |
+
143,106.669429211675,10.7808608330374,"[82, 87]"
|
| 143 |
+
144,106.664583574125,10.7771453550902,"[82, 86]"
|
| 144 |
+
145,106.662639535187,10.7787637920943,[86]
|
| 145 |
+
146,106.660676166525,10.7825988728502,"[86, 84]"
|
| 146 |
+
147,106.665573266675,10.7838583319348,[87]
|
| 147 |
+
148,106.667095293854,10.7676434331484,"[83, 88, 89]"
|
| 148 |
+
149,106.667598708896,10.7656176915395,[88]
|
| 149 |
+
150,106.642347624625,10.7541871496533,"[89, 33]"
|
| 150 |
+
151,106.649304342063,10.7579029221914,[89]
|
| 151 |
+
152,106.653320951438,10.7601071575589,[89]
|
| 152 |
+
153,106.659972456562,10.7637677265603,"[89, 47]"
|
| 153 |
+
154,106.661611283187,10.764586434466,[89]
|
| 154 |
+
155,106.664053381687,10.7658696016644,[89]
|
| 155 |
+
156,106.668005725312,10.7680947601942,"[28, 89]"
|
| 156 |
+
157,106.6744483395,10.7676565531207,"[28, 90, 92]"
|
| 157 |
+
158,106.6786898625,10.7663340599168,[28]
|
| 158 |
+
159,106.681662141875,10.7653579092233,"[28, 41, 93, 94, 49]"
|
| 159 |
+
160,106.676467347292,10.7621749198336,"[90, 93]"
|
| 160 |
+
161,106.673098786,10.7708289624133,"[90, 89]"
|
| 161 |
+
162,106.677666969958,10.7588266144244,"[90, 31]"
|
| 162 |
+
163,106.67819501815,10.7571692145631,"[90, 95]"
|
| 163 |
+
164,106.678984412708,10.7547014797503,"[90, 50]"
|
| 164 |
+
165,106.6777901455,10.7737258522885,"[91, 89]"
|
| 165 |
+
166,106.67903046965,10.7726741553121,[91]
|
| 166 |
+
167,106.68210236055,10.7700039785576,[91]
|
| 167 |
+
168,106.68428738755,10.7680643218585,"[91, 41]"
|
| 168 |
+
169,106.676906494875,10.7744185721221,[91]
|
| 169 |
+
170,106.671909869625,10.7749066221914,[91]
|
| 170 |
+
171,106.679926973375,10.7752057496533,[89]
|
| 171 |
+
172,106.681662141875,10.7778034355062,"[89, 46]"
|
| 172 |
+
173,106.666142018562,10.7560870220527,"[92, 93, 31, 33]"
|
| 173 |
+
174,106.668905445812,10.7598657436893,"[92, 88, 36]"
|
| 174 |
+
175,106.670683464896,10.7623113711512,[92]
|
| 175 |
+
176,106.671636740187,10.7734739488211,[90]
|
| 176 |
+
177,106.689502518688,10.7735447966713,"[41, 46]"
|
| 177 |
+
178,106.687205018125,10.7711879248544,[41]
|
| 178 |
+
179,106.68446733095,10.7683162253259,[41]
|
| 179 |
+
180,106.6917357535,10.775906337656,[41]
|
| 180 |
+
181,106.693053201375,10.7773232572122,[41]
|
| 181 |
+
182,106.69398505475,10.7782678702497,[41]
|
| 182 |
+
183,106.694997240313,10.7794328929958,[41]
|
| 183 |
+
184,106.69588732095,10.7804011213592,[41]
|
| 184 |
+
185,106.696973412125,10.7816054744972,[41]
|
| 185 |
+
186,106.69809806275,10.7828019606276,[41]
|
| 186 |
+
187,106.69964049075,10.7844864871533,[41]
|
| 187 |
+
188,106.701632729,10.7865016216886,[41]
|
| 188 |
+
189,106.703512502187,10.7885010129854,[41]
|
| 189 |
+
190,106.70737808705,10.7926430126838,[41]
|
| 190 |
+
191,106.7099872765,10.7954011551803,[41]
|
| 191 |
+
192,106.678673796125,10.7636234030513,[93]
|
| 192 |
+
193,106.675064217208,10.7612092643551,"[93, 36]"
|
| 193 |
+
194,106.671368966229,10.7588685994452,[93]
|
| 194 |
+
195,106.669430282771,10.7575670331484,"[93, 88]"
|
| 195 |
+
196,106.668037858187,10.7636890046463,[88]
|
| 196 |
+
197,106.668466296521,10.7618075509015,[88]
|
| 197 |
+
198,106.669655212896,10.7566433382802,"[88, 31]"
|
| 198 |
+
199,106.669583449475,10.7545870675451,"[88, 95]"
|
| 199 |
+
200,106.669494548521,10.7534313993065,"[88, 94]"
|
| 200 |
+
201,106.669430282771,10.7526546558946,"[88, 50]"
|
| 201 |
+
202,106.66930710675,10.7499360216366,[88]
|
| 202 |
+
203,106.671786693604,10.7570631995839,[31]
|
| 203 |
+
204,106.675792559542,10.7580498736477,[31]
|
| 204 |
+
205,106.680162613542,10.7598447511789,[31]
|
| 205 |
+
206,106.683375888542,10.7611357905687,"[31, 49]"
|
| 206 |
+
207,106.667887904771,10.7525706836338,"[94, 50]"
|
| 207 |
+
208,106.671229723771,10.7543970803051,[94]
|
| 208 |
+
209,106.671961282225,10.7550531135922,"[94, 95]"
|
| 209 |
+
210,106.677099291375,10.7604640302358,[94]
|
| 210 |
+
211,106.679091521875,10.7625003037448,[94]
|
| 211 |
+
212,106.68404317865,10.7594742333703,"[49, 95]"
|
| 212 |
+
213,106.685228877125,10.7564544006935,"[49, 50]"
|
| 213 |
+
214,106.68669091725,10.7540297016644,[49]
|
| 214 |
+
215,106.687783409875,10.7506445563107,[49]
|
| 215 |
+
216,106.658762118271,10.752927565742,[95]
|
| 216 |
+
217,106.657391115604,10.7528855796117,[95]
|
| 217 |
+
218,106.653567303479,10.7526756489598,[95]
|
| 218 |
+
219,106.650734255,10.7532582065187,"[95, 33]"
|
| 219 |
+
220,106.661482751688,10.7530010414702,[95]
|
| 220 |
+
221,106.662885887229,10.7532424617198,[95]
|
| 221 |
+
222,106.664181913187,10.7534523923717,[95]
|
| 222 |
+
223,106.666602589771,10.7539562259362,[95]
|
| 223 |
+
224,106.67626705315,10.7563504850208,[95]
|
| 224 |
+
225,106.68077849125,10.7582146692094,[95]
|
| 225 |
+
226,106.68663950485,10.7625223458807,[95]
|
| 226 |
+
227,106.687089339775,10.7633158627739,[95]
|
| 227 |
+
228,106.688336095325,10.7666788527878,[95]
|
| 228 |
+
229,106.689762794975,10.7686814853537,[95]
|
| 229 |
+
230,106.693284558075,10.7713516621082,"[95, 46]"
|
| 230 |
+
231,106.654421502406,10.7519277710125,[50]
|
| 231 |
+
232,106.658920104906,10.7519592606103,[50]
|
| 232 |
+
233,106.661482751688,10.7520563535368,[50]
|
| 233 |
+
234,106.664417554271,10.752255787656,[50]
|
| 234 |
+
235,106.656105800604,10.751972381276,[50]
|
| 235 |
+
236,106.657487514229,10.751972381276,[50]
|
| 236 |
+
237,106.659980489781,10.751990750208,[50]
|
| 237 |
+
238,106.666773965104,10.7524657183079,[50]
|
| 238 |
+
239,106.672065178521,10.7529380622746,[50]
|
| 239 |
+
240,106.674775022458,10.7535888472954,[50]
|
| 240 |
+
241,106.676885073042,10.7541766531207,[50]
|
| 241 |
+
242,106.681415790792,10.7553732578363,[50]
|
| 242 |
+
243,106.683686505125,10.7559820567268,[50]
|
| 243 |
+
244,106.687598645844,10.759028667337,[50]
|
| 244 |
+
245,106.689783681344,10.7615792573509,[50]
|
| 245 |
+
246,106.692434643531,10.7646179232316,[50]
|
| 246 |
+
247,106.694314416719,10.7668063789182,[50]
|
| 247 |
+
248,106.695406934469,10.7680029203883,[50]
|
| 248 |
+
249,106.634458953812,10.7538092744799,"[33, 30]"
|
| 249 |
+
250,106.643175046156,10.7541005532594,"[33, 96]"
|
| 250 |
+
251,106.6480351435,10.7535416128988,[33]
|
| 251 |
+
252,106.653770811688,10.7535731024965,[33]
|
| 252 |
+
253,106.655056126687,10.753793529681,[33]
|
| 253 |
+
254,106.658462211437,10.7543760872399,[33]
|
| 254 |
+
255,106.656132578,10.7540139568655,[33]
|
| 255 |
+
256,106.660325918187,10.7548799208044,[33]
|
| 256 |
+
257,106.661547017437,10.7551108445215,[33]
|
| 257 |
+
258,106.662437098075,10.7552609449376,"[33, 47]"
|
| 258 |
+
259,106.663432146104,10.7555097127601,[33]
|
| 259 |
+
260,106.663774896771,10.755562195423,[33]
|
| 260 |
+
261,106.642339591406,10.7496919708044,"[96, 99]"
|
| 261 |
+
262,106.643279478,10.7546437488211,[96]
|
| 262 |
+
263,106.643938201938,10.758249307767,[96]
|
| 263 |
+
264,106.642741252344,10.7467948512483,"[96, 97]"
|
| 264 |
+
265,106.642917983156,10.7458501383495,"[97, 98]"
|
| 265 |
+
266,106.643271444781,10.7427797803571,[97]
|
| 266 |
+
267,106.643881969406,10.7400400586512,[97]
|
| 267 |
+
268,106.644412161844,10.743425346966,[98]
|
| 268 |
+
269,106.645729609719,10.7411894821255,[98]
|
| 269 |
+
270,106.640009957969,10.7492038691401,[99]
|
| 270 |
+
271,106.635189976719,10.7479757423717,[99]
|
| 271 |
+
272,106.631446496781,10.7470782651179,[99]
|
| 272 |
+
273,106.628859800344,10.7464169660888,[99]
|
| 273 |
+
274,106.625630446406,10.7456139601248,[99]
|
| 274 |
+
275,106.623750673219,10.7454407627601,"[99, 30]"
|
| 275 |
+
276,106.665587726469,10.7591703667822,[36]
|
| 276 |
+
277,106.671069059396,10.7603380751734,[36]
|
| 277 |
+
278,106.662760033469,10.7585878209431,[36]
|
| 278 |
+
279,106.661522917781,10.7583516489598,"[36, 47]"
|
| 279 |
+
280,106.660623147281,10.7581627113731,[36]
|
| 280 |
+
281,106.659498496656,10.7579422841886,[36]
|
| 281 |
+
282,106.658277447406,10.7576746226075,[36]
|
| 282 |
+
283,106.657088531031,10.7574384506241,[36]
|
| 283 |
+
284,106.656012079719,10.7572337682386,[36]
|
| 284 |
+
285,106.653666379844,10.7567771690707,[36]
|
| 285 |
+
286,106.651127882719,10.7562890803051,"[36, 100]"
|
| 286 |
+
287,106.651401012156,10.7590601561026,"[100, 89]"
|
| 287 |
+
288,106.650469158781,10.7509673332178,[100]
|
| 288 |
+
289,106.601410291875,10.7236563842233,[30]
|
| 289 |
+
290,106.604157652687,10.726034084466,[30]
|
| 290 |
+
291,106.607242408687,10.728081111165,[30]
|
| 291 |
+
292,106.6102468825,10.7301596355062,[30]
|
| 292 |
+
293,106.610985938625,10.7312775974688,[30]
|
| 293 |
+
294,106.611933858437,10.7328049539528,[30]
|
| 294 |
+
295,106.61346017,10.7353557967406,[30]
|
| 295 |
+
296,106.614986481563,10.7379537763176,[30]
|
| 296 |
+
297,106.618296167688,10.7410241540915,[30]
|
| 297 |
+
298,106.628851767125,10.7496840981969,[30]
|
| 298 |
+
300,106.656807368375,10.775073503828,[47]
|
| 299 |
+
301,106.660226306275,10.7628561267961,[47]
|
| 300 |
+
302,106.660509075575,10.761961845853,[47]
|
| 301 |
+
303,106.660766138575,10.7610423738974,[47]
|
| 302 |
+
304,106.661061811025,10.760135497448,[47]
|
| 303 |
+
305,106.661331727175,10.7592160254924,[47]
|
| 304 |
+
306,106.655316402975,10.7804640955617,[47]
|
| 305 |
+
307,106.654429535625,10.7836127374133,[47]
|
| 306 |
+
308,106.654223885225,10.7843810074549,[47]
|
| 307 |
+
309,106.653851143875,10.7856152773578,[47]
|
| 308 |
+
310,106.653542668275,10.7866858175797,[47]
|
| 309 |
+
311,106.655701997475,10.7953255896325,[23]
|
| 310 |
+
312,106.658401158975,10.7978821391609,[23]
|
| 311 |
+
313,106.660611900775,10.7999853246533,[23]
|
| 312 |
+
314,106.665470441475,10.8003253606311,[23]
|
| 313 |
+
315,106.667552651775,10.8001742335298,[23]
|
| 314 |
+
316,106.673015240525,10.7996830704508,[23]
|
| 315 |
+
317,106.67491747765,10.7995697251248,[23]
|
| 316 |
+
318,106.67752665695,10.7993556283981,[23]
|
| 317 |
+
319,106.68027722035,10.7991415316713,[23]
|
| 318 |
+
320,106.660615114062,10.7889890533807,[46]
|
| 319 |
+
321,106.657176896438,10.7907837853502,[46]
|
| 320 |
+
322,106.66647941375,10.7858089191921,[46]
|
| 321 |
+
323,106.67711535775,10.7801728398752,[46]
|
| 322 |
+
324,106.678641663375,10.7793384316921,[46]
|
| 323 |
+
325,106.6837025715,10.776662028086,[46]
|
| 324 |
+
326,106.686835490062,10.7749932117198,[46]
|
| 325 |
+
327,106.688281469437,10.7742375212899,[46]
|
| 326 |
+
328,106.628867833563,10.7820384135576,[101]
|
| 327 |
+
329,106.621429123,10.7843526696255,"[101, 102]"
|
| 328 |
+
330,106.622023581188,10.7860844258669,[102]
|
| 329 |
+
331,106.623212447563,10.7897998589459,[102]
|
| 330 |
+
332,106.620127741563,10.7804168649098,[102]
|
| 331 |
+
333,106.627036259688,10.7717421124827,[77]
|
| 332 |
+
334,106.632643446375,10.755808863939,[103]
|
| 333 |
+
335,106.631872257375,10.7543760872399,[103]
|
| 334 |
+
336,106.631534862188,10.7534943785021,[103]
|
| 335 |
+
337,106.632386383375,10.7524552217753,"[103, 30]"
|
backend/export_positions.json
ADDED
|
@@ -0,0 +1,1672 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": 1,
|
| 4 |
+
"long": 106.636137946531,
|
| 5 |
+
"lat": 10.8027874729889
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"id": 2,
|
| 9 |
+
"long": 106.635414956844,
|
| 10 |
+
"lat": 10.8025041096741
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"id": 3,
|
| 14 |
+
"long": 106.633984993906,
|
| 15 |
+
"lat": 10.8019373830444
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"id": 4,
|
| 19 |
+
"long": 106.631783891969,
|
| 20 |
+
"lat": 10.798442568828
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"id": 5,
|
| 24 |
+
"long": 106.629414092438,
|
| 25 |
+
"lat": 10.7947982050797
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"id": 6,
|
| 29 |
+
"long": 106.628401906875,
|
| 30 |
+
"lat": 10.7928382236824
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"id": 7,
|
| 34 |
+
"long": 106.629815753375,
|
| 35 |
+
"lat": 10.799229689147
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"id": 8,
|
| 39 |
+
"long": 106.627502186375,
|
| 40 |
+
"lat": 10.8003631424064
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"id": 9,
|
| 44 |
+
"long": 106.633037074094,
|
| 45 |
+
"lat": 10.7976397061026
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"id": 10,
|
| 49 |
+
"long": 106.632386383375,
|
| 50 |
+
"lat": 10.7938851213766
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"id": 11,
|
| 54 |
+
"long": 106.6308118725,
|
| 55 |
+
"lat": 10.7941212637136
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"id": 12,
|
| 59 |
+
"long": 106.634266156563,
|
| 60 |
+
"lat": 10.7935860077497
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"id": 13,
|
| 64 |
+
"long": 106.637849022125,
|
| 65 |
+
"lat": 10.7930035233183
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"id": 14,
|
| 69 |
+
"long": 106.627935980188,
|
| 70 |
+
"lat": 10.7956325746706
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"id": 15,
|
| 74 |
+
"long": 106.626923794625,
|
| 75 |
+
"lat": 10.7956955459605
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"id": 16,
|
| 79 |
+
"long": 106.623822972188,
|
| 80 |
+
"lat": 10.7958057457178
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": 17,
|
| 84 |
+
"long": 106.622714388,
|
| 85 |
+
"lat": 10.7957427744279
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"id": 18,
|
| 89 |
+
"long": 106.620143808,
|
| 90 |
+
"lat": 10.7956168318481
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"id": 19,
|
| 94 |
+
"long": 106.623967570125,
|
| 95 |
+
"lat": 10.7992060755374
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"id": 20,
|
| 99 |
+
"long": 106.62457809475,
|
| 100 |
+
"lat": 10.8013155579924
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"id": 21,
|
| 104 |
+
"long": 106.619035223813,
|
| 105 |
+
"lat": 10.7957112887829
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"id": 22,
|
| 109 |
+
"long": 106.616753789688,
|
| 110 |
+
"lat": 10.795915945475
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"id": 23,
|
| 114 |
+
"long": 106.61442415625,
|
| 115 |
+
"lat": 10.7950500902393
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"id": 24,
|
| 119 |
+
"long": 106.626931827844,
|
| 120 |
+
"lat": 10.7930979802531
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"id": 25,
|
| 124 |
+
"long": 106.625542081,
|
| 125 |
+
"lat": 10.7933105083564
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"id": 26,
|
| 129 |
+
"long": 106.628980298625,
|
| 130 |
+
"lat": 10.7927752523925
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"id": 27,
|
| 134 |
+
"long": 106.631117134812,
|
| 135 |
+
"lat": 10.7924446531207
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"id": 28,
|
| 139 |
+
"long": 106.633993027125,
|
| 140 |
+
"lat": 10.7920038540915
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"id": 29,
|
| 144 |
+
"long": 106.637591959125,
|
| 145 |
+
"lat": 10.7914528553051
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"id": 30,
|
| 149 |
+
"long": 106.638290849156,
|
| 150 |
+
"lat": 10.7955066320908
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"id": 31,
|
| 154 |
+
"long": 106.637358995781,
|
| 155 |
+
"lat": 10.7901225868065
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"id": 32,
|
| 159 |
+
"long": 106.636748471156,
|
| 160 |
+
"lat": 10.7863835473994
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"id": 33,
|
| 164 |
+
"long": 106.63601744825,
|
| 165 |
+
"lat": 10.7815897312587
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"id": 34,
|
| 169 |
+
"long": 106.635864817094,
|
| 170 |
+
"lat": 10.7804168649098
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"id": 35,
|
| 174 |
+
"long": 106.634129591844,
|
| 175 |
+
"lat": 10.7765675667822
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"id": 36,
|
| 179 |
+
"long": 106.633519067219,
|
| 180 |
+
"lat": 10.7757646457004
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"id": 37,
|
| 184 |
+
"long": 106.631783891969,
|
| 185 |
+
"lat": 10.7701440998613
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"id": 38,
|
| 189 |
+
"long": 106.632346217281,
|
| 190 |
+
"lat": 10.7639487869626
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"id": 39,
|
| 194 |
+
"long": 106.634209924031,
|
| 195 |
+
"lat": 10.7588239903606
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"id": 40,
|
| 199 |
+
"long": 106.637567859469,
|
| 200 |
+
"lat": 10.7545335352288
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"id": 41,
|
| 204 |
+
"long": 106.633462834688,
|
| 205 |
+
"lat": 10.7876036483877
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"id": 42,
|
| 209 |
+
"long": 106.630394145125,
|
| 210 |
+
"lat": 10.787981486113
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"id": 43,
|
| 214 |
+
"long": 106.627646784313,
|
| 215 |
+
"lat": 10.7875091889563
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"id": 44,
|
| 219 |
+
"long": 106.62277865375,
|
| 220 |
+
"lat": 10.7884222967926
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"id": 45,
|
| 224 |
+
"long": 106.633784163438,
|
| 225 |
+
"lat": 10.7907050712379
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"id": 46,
|
| 229 |
+
"long": 106.630956470438,
|
| 230 |
+
"lat": 10.7911773559119
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"id": 47,
|
| 234 |
+
"long": 106.629751487625,
|
| 235 |
+
"lat": 10.791413498249
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"id": 48,
|
| 239 |
+
"long": 106.643440142375,
|
| 240 |
+
"lat": 10.7866511824549
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"id": 49,
|
| 244 |
+
"long": 106.646107171,
|
| 245 |
+
"lat": 10.7879106415395
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"id": 50,
|
| 249 |
+
"long": 106.641624634938,
|
| 250 |
+
"lat": 10.7858876353849
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"id": 51,
|
| 254 |
+
"long": 106.643327677312,
|
| 255 |
+
"lat": 10.7813063755201
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"id": 52,
|
| 259 |
+
"long": 106.640194722,
|
| 260 |
+
"lat": 10.7900202584605
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"id": 53,
|
| 264 |
+
"long": 106.639311067937,
|
| 265 |
+
"lat": 10.7926807954577
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"id": 54,
|
| 269 |
+
"long": 106.644532660125,
|
| 270 |
+
"lat": 10.7795903285021
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"id": 55,
|
| 274 |
+
"long": 106.645994705937,
|
| 275 |
+
"lat": 10.7774334620666
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"id": 56,
|
| 279 |
+
"long": 106.648019077063,
|
| 280 |
+
"lat": 10.7747413149098
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"id": 57,
|
| 284 |
+
"long": 106.640226854875,
|
| 285 |
+
"lat": 10.7848092235437
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"id": 58,
|
| 289 |
+
"long": 106.634185824375,
|
| 290 |
+
"lat": 10.780621531068
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"id": 59,
|
| 294 |
+
"long": 106.628610770563,
|
| 295 |
+
"lat": 10.7775357951456
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"id": 60,
|
| 299 |
+
"long": 106.621445189438,
|
| 300 |
+
"lat": 10.7735999005548
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"id": 61,
|
| 304 |
+
"long": 106.642042362312,
|
| 305 |
+
"lat": 10.7848800681172
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"id": 62,
|
| 309 |
+
"long": 106.6436650725,
|
| 310 |
+
"lat": 10.7854153382282
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"id": 63,
|
| 314 |
+
"long": 106.643070614313,
|
| 315 |
+
"lat": 10.7880759455444
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"id": 64,
|
| 319 |
+
"long": 106.642877817062,
|
| 320 |
+
"lat": 10.7893197028606
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"id": 65,
|
| 324 |
+
"long": 106.645834041562,
|
| 325 |
+
"lat": 10.7968604569868
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"id": 66,
|
| 329 |
+
"long": 106.646099137781,
|
| 330 |
+
"lat": 10.7720255038835
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"id": 67,
|
| 334 |
+
"long": 106.644492494031,
|
| 335 |
+
"lat": 10.7697583726768
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"id": 68,
|
| 339 |
+
"long": 106.642355657844,
|
| 340 |
+
"lat": 10.7667512750347
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"id": 69,
|
| 344 |
+
"long": 106.649762235531,
|
| 345 |
+
"lat": 10.7771107192788
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"id": 70,
|
| 349 |
+
"long": 106.649890767031,
|
| 350 |
+
"lat": 10.7794250212205
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"id": 71,
|
| 354 |
+
"long": 106.650163896469,
|
| 355 |
+
"lat": 10.7821092581311
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"id": 72,
|
| 359 |
+
"long": 106.651015417656,
|
| 360 |
+
"lat": 10.7845966898232
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"id": 73,
|
| 364 |
+
"long": 106.652450150525,
|
| 365 |
+
"lat": 10.7901619438627
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"id": 74,
|
| 369 |
+
"long": 106.642259259219,
|
| 370 |
+
"lat": 10.7628466801664
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"id": 75,
|
| 374 |
+
"long": 106.641568402406,
|
| 375 |
+
"lat": 10.7589263288488
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"id": 76,
|
| 379 |
+
"long": 106.637599992344,
|
| 380 |
+
"lat": 10.7586271829404
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"id": 79,
|
| 384 |
+
"long": 106.635390857188,
|
| 385 |
+
"lat": 10.7575880262136
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"id": 80,
|
| 389 |
+
"long": 106.629598856469,
|
| 390 |
+
"lat": 10.8231887713592
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"id": 81,
|
| 394 |
+
"long": 106.630675307781,
|
| 395 |
+
"lat": 10.8204183092233
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"id": 82,
|
| 399 |
+
"long": 106.632820177188,
|
| 400 |
+
"lat": 10.813641559414
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"id": 83,
|
| 404 |
+
"long": 106.634651751063,
|
| 405 |
+
"lat": 10.807533768568
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"id": 84,
|
| 409 |
+
"long": 106.635776451688,
|
| 410 |
+
"lat": 10.8037713733877
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"id": 85,
|
| 414 |
+
"long": 106.63666010575,
|
| 415 |
+
"lat": 10.8018586710125
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"id": 86,
|
| 419 |
+
"long": 106.641174774687,
|
| 420 |
+
"lat": 10.7993713708044
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"id": 87,
|
| 424 |
+
"long": 106.644050667,
|
| 425 |
+
"lat": 10.7978443573856
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"id": 88,
|
| 429 |
+
"long": 106.646926559313,
|
| 430 |
+
"lat": 10.7962701589806
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"id": 89,
|
| 434 |
+
"long": 106.648501020187,
|
| 435 |
+
"lat": 10.7954200465673
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"id": 90,
|
| 439 |
+
"long": 106.650163896469,
|
| 440 |
+
"lat": 10.794569934154
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"id": 91,
|
| 444 |
+
"long": 106.653361117531,
|
| 445 |
+
"lat": 10.7928067380374
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"id": 92,
|
| 449 |
+
"long": 106.630803839281,
|
| 450 |
+
"lat": 10.8255342194175
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"id": 93,
|
| 454 |
+
"long": 106.640122423031,
|
| 455 |
+
"lat": 10.844297018724
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"id": 94,
|
| 459 |
+
"long": 106.634916847281,
|
| 460 |
+
"lat": 10.8344593117198
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"id": 95,
|
| 464 |
+
"long": 106.635913016406,
|
| 465 |
+
"lat": 10.8364426058946
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"id": 96,
|
| 469 |
+
"long": 106.636049581125,
|
| 470 |
+
"lat": 10.8062744093447
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"id": 97,
|
| 474 |
+
"long": 106.637591959125,
|
| 475 |
+
"lat": 10.8041649826456
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"id": 98,
|
| 479 |
+
"long": 106.638732676188,
|
| 480 |
+
"lat": 10.8028110865985
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"id": 99,
|
| 484 |
+
"long": 106.641640701375,
|
| 485 |
+
"lat": 10.8023230720007
|
| 486 |
+
},
|
| 487 |
+
{
|
| 488 |
+
"id": 100,
|
| 489 |
+
"long": 106.64745675175,
|
| 490 |
+
"lat": 10.8018665422157
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"id": 101,
|
| 494 |
+
"long": 106.654509867813,
|
| 495 |
+
"lat": 10.8012683307732
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"id": 102,
|
| 499 |
+
"long": 106.661293167725,
|
| 500 |
+
"lat": 10.800564645208
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"id": 103,
|
| 504 |
+
"long": 106.640106356594,
|
| 505 |
+
"lat": 10.7931452087205
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
"id": 104,
|
| 509 |
+
"long": 106.640829346281,
|
| 510 |
+
"lat": 10.7955617319695
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"id": 105,
|
| 514 |
+
"long": 106.641953996906,
|
| 515 |
+
"lat": 10.7989620682386
|
| 516 |
+
},
|
| 517 |
+
{
|
| 518 |
+
"id": 106,
|
| 519 |
+
"long": 106.647223788406,
|
| 520 |
+
"lat": 10.7994185980236
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
"id": 107,
|
| 524 |
+
"long": 106.648107442469,
|
| 525 |
+
"lat": 10.8093991944175
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"id": 108,
|
| 529 |
+
"long": 106.644862022094,
|
| 530 |
+
"lat": 10.8096746792476
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"id": 109,
|
| 534 |
+
"long": 106.640210788437,
|
| 535 |
+
"lat": 10.8078249953883
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"id": 110,
|
| 539 |
+
"long": 106.655048093469,
|
| 540 |
+
"lat": 10.8086435788835
|
| 541 |
+
},
|
| 542 |
+
{
|
| 543 |
+
"id": 111,
|
| 544 |
+
"long": 106.652196300813,
|
| 545 |
+
"lat": 10.7688609665742
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
"id": 112,
|
| 549 |
+
"long": 106.646396366875,
|
| 550 |
+
"lat": 10.7652240819695
|
| 551 |
+
},
|
| 552 |
+
{
|
| 553 |
+
"id": 113,
|
| 554 |
+
"long": 106.651151982375,
|
| 555 |
+
"lat": 10.7626971085298
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"id": 114,
|
| 559 |
+
"long": 106.6570322985,
|
| 560 |
+
"lat": 10.7621460551318
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
"id": 115,
|
| 564 |
+
"long": 106.644853988875,
|
| 565 |
+
"lat": 10.7658144963245
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
"id": 116,
|
| 569 |
+
"long": 106.637583925906,
|
| 570 |
+
"lat": 10.7689081984743
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"id": 117,
|
| 574 |
+
"long": 106.63563185375,
|
| 575 |
+
"lat": 10.7694120054092
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"id": 118,
|
| 579 |
+
"long": 106.644942354281,
|
| 580 |
+
"lat": 10.7642243136616
|
| 581 |
+
},
|
| 582 |
+
{
|
| 583 |
+
"id": 119,
|
| 584 |
+
"long": 106.654558067125,
|
| 585 |
+
"lat": 10.7656727968793
|
| 586 |
+
},
|
| 587 |
+
{
|
| 588 |
+
"id": 120,
|
| 589 |
+
"long": 106.652758626125,
|
| 590 |
+
"lat": 10.7711359697642
|
| 591 |
+
},
|
| 592 |
+
{
|
| 593 |
+
"id": 121,
|
| 594 |
+
"long": 106.653272752125,
|
| 595 |
+
"lat": 10.7755757230929
|
| 596 |
+
},
|
| 597 |
+
{
|
| 598 |
+
"id": 122,
|
| 599 |
+
"long": 106.655907647875,
|
| 600 |
+
"lat": 10.7783465880028
|
| 601 |
+
},
|
| 602 |
+
{
|
| 603 |
+
"id": 123,
|
| 604 |
+
"long": 106.65812481625,
|
| 605 |
+
"lat": 10.7702543076283
|
| 606 |
+
},
|
| 607 |
+
{
|
| 608 |
+
"id": 124,
|
| 609 |
+
"long": 106.663606734725,
|
| 610 |
+
"lat": 10.7763896646602
|
| 611 |
+
},
|
| 612 |
+
{
|
| 613 |
+
"id": 125,
|
| 614 |
+
"long": 106.672054467562,
|
| 615 |
+
"lat": 10.7828649335818
|
| 616 |
+
},
|
| 617 |
+
{
|
| 618 |
+
"id": 126,
|
| 619 |
+
"long": 106.661418485938,
|
| 620 |
+
"lat": 10.7734975647712
|
| 621 |
+
},
|
| 622 |
+
{
|
| 623 |
+
"id": 127,
|
| 624 |
+
"long": 106.664789224525,
|
| 625 |
+
"lat": 10.7733416995007
|
| 626 |
+
},
|
| 627 |
+
{
|
| 628 |
+
"id": 128,
|
| 629 |
+
"long": 106.658851019225,
|
| 630 |
+
"lat": 10.7807348846325
|
| 631 |
+
},
|
| 632 |
+
{
|
| 633 |
+
"id": 129,
|
| 634 |
+
"long": 106.664596427275,
|
| 635 |
+
"lat": 10.7868558445562
|
| 636 |
+
},
|
| 637 |
+
{
|
| 638 |
+
"id": 130,
|
| 639 |
+
"long": 106.661794440575,
|
| 640 |
+
"lat": 10.7837449806172
|
| 641 |
+
},
|
| 642 |
+
{
|
| 643 |
+
"id": 131,
|
| 644 |
+
"long": 106.656087591975,
|
| 645 |
+
"lat": 10.7778128816366
|
| 646 |
+
},
|
| 647 |
+
{
|
| 648 |
+
"id": 132,
|
| 649 |
+
"long": 106.665573266675,
|
| 650 |
+
"lat": 10.7778506661581
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
"id": 133,
|
| 654 |
+
"long": 106.668015365175,
|
| 655 |
+
"lat": 10.7746137921498
|
| 656 |
+
},
|
| 657 |
+
{
|
| 658 |
+
"id": 134,
|
| 659 |
+
"long": 106.670769152562,
|
| 660 |
+
"lat": 10.769637668932
|
| 661 |
+
},
|
| 662 |
+
{
|
| 663 |
+
"id": 135,
|
| 664 |
+
"long": 106.671101192271,
|
| 665 |
+
"lat": 10.7698161005548
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"id": 136,
|
| 669 |
+
"long": 106.671551052521,
|
| 670 |
+
"lat": 10.7677903768377
|
| 671 |
+
},
|
| 672 |
+
{
|
| 673 |
+
"id": 137,
|
| 674 |
+
"long": 106.671818826479,
|
| 675 |
+
"lat": 10.7666463152566
|
| 676 |
+
},
|
| 677 |
+
{
|
| 678 |
+
"id": 138,
|
| 679 |
+
"long": 106.672268686729,
|
| 680 |
+
"lat": 10.7646100510402
|
| 681 |
+
},
|
| 682 |
+
{
|
| 683 |
+
"id": 139,
|
| 684 |
+
"long": 106.672675703146,
|
| 685 |
+
"lat": 10.76277320638
|
| 686 |
+
},
|
| 687 |
+
{
|
| 688 |
+
"id": 140,
|
| 689 |
+
"long": 106.673146985312,
|
| 690 |
+
"lat": 10.7607474291262
|
| 691 |
+
},
|
| 692 |
+
{
|
| 693 |
+
"id": 141,
|
| 694 |
+
"long": 106.673296938729,
|
| 695 |
+
"lat": 10.7600756687933
|
| 696 |
+
},
|
| 697 |
+
{
|
| 698 |
+
"id": 142,
|
| 699 |
+
"long": 106.674089523792,
|
| 700 |
+
"lat": 10.7575040539528
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"id": 143,
|
| 704 |
+
"long": 106.669429211675,
|
| 705 |
+
"lat": 10.7808608330374
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"id": 144,
|
| 709 |
+
"long": 106.664583574125,
|
| 710 |
+
"lat": 10.7771453550902
|
| 711 |
+
},
|
| 712 |
+
{
|
| 713 |
+
"id": 145,
|
| 714 |
+
"long": 106.662639535187,
|
| 715 |
+
"lat": 10.7787637920943
|
| 716 |
+
},
|
| 717 |
+
{
|
| 718 |
+
"id": 146,
|
| 719 |
+
"long": 106.660676166525,
|
| 720 |
+
"lat": 10.7825988728502
|
| 721 |
+
},
|
| 722 |
+
{
|
| 723 |
+
"id": 147,
|
| 724 |
+
"long": 106.665573266675,
|
| 725 |
+
"lat": 10.7838583319348
|
| 726 |
+
},
|
| 727 |
+
{
|
| 728 |
+
"id": 148,
|
| 729 |
+
"long": 106.667095293854,
|
| 730 |
+
"lat": 10.7676434331484
|
| 731 |
+
},
|
| 732 |
+
{
|
| 733 |
+
"id": 149,
|
| 734 |
+
"long": 106.667598708896,
|
| 735 |
+
"lat": 10.7656176915395
|
| 736 |
+
},
|
| 737 |
+
{
|
| 738 |
+
"id": 150,
|
| 739 |
+
"long": 106.642347624625,
|
| 740 |
+
"lat": 10.7541871496533
|
| 741 |
+
},
|
| 742 |
+
{
|
| 743 |
+
"id": 151,
|
| 744 |
+
"long": 106.649304342063,
|
| 745 |
+
"lat": 10.7579029221914
|
| 746 |
+
},
|
| 747 |
+
{
|
| 748 |
+
"id": 152,
|
| 749 |
+
"long": 106.653320951438,
|
| 750 |
+
"lat": 10.7601071575589
|
| 751 |
+
},
|
| 752 |
+
{
|
| 753 |
+
"id": 153,
|
| 754 |
+
"long": 106.659972456562,
|
| 755 |
+
"lat": 10.7637677265603
|
| 756 |
+
},
|
| 757 |
+
{
|
| 758 |
+
"id": 154,
|
| 759 |
+
"long": 106.661611283187,
|
| 760 |
+
"lat": 10.764586434466
|
| 761 |
+
},
|
| 762 |
+
{
|
| 763 |
+
"id": 155,
|
| 764 |
+
"long": 106.664053381687,
|
| 765 |
+
"lat": 10.7658696016644
|
| 766 |
+
},
|
| 767 |
+
{
|
| 768 |
+
"id": 156,
|
| 769 |
+
"long": 106.668005725312,
|
| 770 |
+
"lat": 10.7680947601942
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"id": 157,
|
| 774 |
+
"long": 106.6744483395,
|
| 775 |
+
"lat": 10.7676565531207
|
| 776 |
+
},
|
| 777 |
+
{
|
| 778 |
+
"id": 158,
|
| 779 |
+
"long": 106.6786898625,
|
| 780 |
+
"lat": 10.7663340599168
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"id": 159,
|
| 784 |
+
"long": 106.681662141875,
|
| 785 |
+
"lat": 10.7653579092233
|
| 786 |
+
},
|
| 787 |
+
{
|
| 788 |
+
"id": 160,
|
| 789 |
+
"long": 106.676467347292,
|
| 790 |
+
"lat": 10.7621749198336
|
| 791 |
+
},
|
| 792 |
+
{
|
| 793 |
+
"id": 161,
|
| 794 |
+
"long": 106.673098786,
|
| 795 |
+
"lat": 10.7708289624133
|
| 796 |
+
},
|
| 797 |
+
{
|
| 798 |
+
"id": 162,
|
| 799 |
+
"long": 106.677666969958,
|
| 800 |
+
"lat": 10.7588266144244
|
| 801 |
+
},
|
| 802 |
+
{
|
| 803 |
+
"id": 163,
|
| 804 |
+
"long": 106.67819501815,
|
| 805 |
+
"lat": 10.7571692145631
|
| 806 |
+
},
|
| 807 |
+
{
|
| 808 |
+
"id": 164,
|
| 809 |
+
"long": 106.678984412708,
|
| 810 |
+
"lat": 10.7547014797503
|
| 811 |
+
},
|
| 812 |
+
{
|
| 813 |
+
"id": 165,
|
| 814 |
+
"long": 106.6777901455,
|
| 815 |
+
"lat": 10.7737258522885
|
| 816 |
+
},
|
| 817 |
+
{
|
| 818 |
+
"id": 166,
|
| 819 |
+
"long": 106.67903046965,
|
| 820 |
+
"lat": 10.7726741553121
|
| 821 |
+
},
|
| 822 |
+
{
|
| 823 |
+
"id": 167,
|
| 824 |
+
"long": 106.68210236055,
|
| 825 |
+
"lat": 10.7700039785576
|
| 826 |
+
},
|
| 827 |
+
{
|
| 828 |
+
"id": 168,
|
| 829 |
+
"long": 106.68428738755,
|
| 830 |
+
"lat": 10.7680643218585
|
| 831 |
+
},
|
| 832 |
+
{
|
| 833 |
+
"id": 169,
|
| 834 |
+
"long": 106.676906494875,
|
| 835 |
+
"lat": 10.7744185721221
|
| 836 |
+
},
|
| 837 |
+
{
|
| 838 |
+
"id": 170,
|
| 839 |
+
"long": 106.671909869625,
|
| 840 |
+
"lat": 10.7749066221914
|
| 841 |
+
},
|
| 842 |
+
{
|
| 843 |
+
"id": 171,
|
| 844 |
+
"long": 106.679926973375,
|
| 845 |
+
"lat": 10.7752057496533
|
| 846 |
+
},
|
| 847 |
+
{
|
| 848 |
+
"id": 172,
|
| 849 |
+
"long": 106.681662141875,
|
| 850 |
+
"lat": 10.7778034355062
|
| 851 |
+
},
|
| 852 |
+
{
|
| 853 |
+
"id": 173,
|
| 854 |
+
"long": 106.666142018562,
|
| 855 |
+
"lat": 10.7560870220527
|
| 856 |
+
},
|
| 857 |
+
{
|
| 858 |
+
"id": 174,
|
| 859 |
+
"long": 106.668905445812,
|
| 860 |
+
"lat": 10.7598657436893
|
| 861 |
+
},
|
| 862 |
+
{
|
| 863 |
+
"id": 175,
|
| 864 |
+
"long": 106.670683464896,
|
| 865 |
+
"lat": 10.7623113711512
|
| 866 |
+
},
|
| 867 |
+
{
|
| 868 |
+
"id": 176,
|
| 869 |
+
"long": 106.671636740187,
|
| 870 |
+
"lat": 10.7734739488211
|
| 871 |
+
},
|
| 872 |
+
{
|
| 873 |
+
"id": 177,
|
| 874 |
+
"long": 106.689502518688,
|
| 875 |
+
"lat": 10.7735447966713
|
| 876 |
+
},
|
| 877 |
+
{
|
| 878 |
+
"id": 178,
|
| 879 |
+
"long": 106.687205018125,
|
| 880 |
+
"lat": 10.7711879248544
|
| 881 |
+
},
|
| 882 |
+
{
|
| 883 |
+
"id": 179,
|
| 884 |
+
"long": 106.68446733095,
|
| 885 |
+
"lat": 10.7683162253259
|
| 886 |
+
},
|
| 887 |
+
{
|
| 888 |
+
"id": 180,
|
| 889 |
+
"long": 106.6917357535,
|
| 890 |
+
"lat": 10.775906337656
|
| 891 |
+
},
|
| 892 |
+
{
|
| 893 |
+
"id": 181,
|
| 894 |
+
"long": 106.693053201375,
|
| 895 |
+
"lat": 10.7773232572122
|
| 896 |
+
},
|
| 897 |
+
{
|
| 898 |
+
"id": 182,
|
| 899 |
+
"long": 106.69398505475,
|
| 900 |
+
"lat": 10.7782678702497
|
| 901 |
+
},
|
| 902 |
+
{
|
| 903 |
+
"id": 183,
|
| 904 |
+
"long": 106.694997240313,
|
| 905 |
+
"lat": 10.7794328929958
|
| 906 |
+
},
|
| 907 |
+
{
|
| 908 |
+
"id": 184,
|
| 909 |
+
"long": 106.69588732095,
|
| 910 |
+
"lat": 10.7804011213592
|
| 911 |
+
},
|
| 912 |
+
{
|
| 913 |
+
"id": 185,
|
| 914 |
+
"long": 106.696973412125,
|
| 915 |
+
"lat": 10.7816054744972
|
| 916 |
+
},
|
| 917 |
+
{
|
| 918 |
+
"id": 186,
|
| 919 |
+
"long": 106.69809806275,
|
| 920 |
+
"lat": 10.7828019606276
|
| 921 |
+
},
|
| 922 |
+
{
|
| 923 |
+
"id": 187,
|
| 924 |
+
"long": 106.69964049075,
|
| 925 |
+
"lat": 10.7844864871533
|
| 926 |
+
},
|
| 927 |
+
{
|
| 928 |
+
"id": 188,
|
| 929 |
+
"long": 106.701632729,
|
| 930 |
+
"lat": 10.7865016216886
|
| 931 |
+
},
|
| 932 |
+
{
|
| 933 |
+
"id": 189,
|
| 934 |
+
"long": 106.703512502187,
|
| 935 |
+
"lat": 10.7885010129854
|
| 936 |
+
},
|
| 937 |
+
{
|
| 938 |
+
"id": 190,
|
| 939 |
+
"long": 106.70737808705,
|
| 940 |
+
"lat": 10.7926430126838
|
| 941 |
+
},
|
| 942 |
+
{
|
| 943 |
+
"id": 191,
|
| 944 |
+
"long": 106.7099872765,
|
| 945 |
+
"lat": 10.7954011551803
|
| 946 |
+
},
|
| 947 |
+
{
|
| 948 |
+
"id": 192,
|
| 949 |
+
"long": 106.678673796125,
|
| 950 |
+
"lat": 10.7636234030513
|
| 951 |
+
},
|
| 952 |
+
{
|
| 953 |
+
"id": 193,
|
| 954 |
+
"long": 106.675064217208,
|
| 955 |
+
"lat": 10.7612092643551
|
| 956 |
+
},
|
| 957 |
+
{
|
| 958 |
+
"id": 194,
|
| 959 |
+
"long": 106.671368966229,
|
| 960 |
+
"lat": 10.7588685994452
|
| 961 |
+
},
|
| 962 |
+
{
|
| 963 |
+
"id": 195,
|
| 964 |
+
"long": 106.669430282771,
|
| 965 |
+
"lat": 10.7575670331484
|
| 966 |
+
},
|
| 967 |
+
{
|
| 968 |
+
"id": 196,
|
| 969 |
+
"long": 106.668037858187,
|
| 970 |
+
"lat": 10.7636890046463
|
| 971 |
+
},
|
| 972 |
+
{
|
| 973 |
+
"id": 197,
|
| 974 |
+
"long": 106.668466296521,
|
| 975 |
+
"lat": 10.7618075509015
|
| 976 |
+
},
|
| 977 |
+
{
|
| 978 |
+
"id": 198,
|
| 979 |
+
"long": 106.669655212896,
|
| 980 |
+
"lat": 10.7566433382802
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"id": 199,
|
| 984 |
+
"long": 106.669583449475,
|
| 985 |
+
"lat": 10.7545870675451
|
| 986 |
+
},
|
| 987 |
+
{
|
| 988 |
+
"id": 200,
|
| 989 |
+
"long": 106.669494548521,
|
| 990 |
+
"lat": 10.7534313993065
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"id": 201,
|
| 994 |
+
"long": 106.669430282771,
|
| 995 |
+
"lat": 10.7526546558946
|
| 996 |
+
},
|
| 997 |
+
{
|
| 998 |
+
"id": 202,
|
| 999 |
+
"long": 106.66930710675,
|
| 1000 |
+
"lat": 10.7499360216366
|
| 1001 |
+
},
|
| 1002 |
+
{
|
| 1003 |
+
"id": 203,
|
| 1004 |
+
"long": 106.671786693604,
|
| 1005 |
+
"lat": 10.7570631995839
|
| 1006 |
+
},
|
| 1007 |
+
{
|
| 1008 |
+
"id": 204,
|
| 1009 |
+
"long": 106.675792559542,
|
| 1010 |
+
"lat": 10.7580498736477
|
| 1011 |
+
},
|
| 1012 |
+
{
|
| 1013 |
+
"id": 205,
|
| 1014 |
+
"long": 106.680162613542,
|
| 1015 |
+
"lat": 10.7598447511789
|
| 1016 |
+
},
|
| 1017 |
+
{
|
| 1018 |
+
"id": 206,
|
| 1019 |
+
"long": 106.683375888542,
|
| 1020 |
+
"lat": 10.7611357905687
|
| 1021 |
+
},
|
| 1022 |
+
{
|
| 1023 |
+
"id": 207,
|
| 1024 |
+
"long": 106.667887904771,
|
| 1025 |
+
"lat": 10.7525706836338
|
| 1026 |
+
},
|
| 1027 |
+
{
|
| 1028 |
+
"id": 208,
|
| 1029 |
+
"long": 106.671229723771,
|
| 1030 |
+
"lat": 10.7543970803051
|
| 1031 |
+
},
|
| 1032 |
+
{
|
| 1033 |
+
"id": 209,
|
| 1034 |
+
"long": 106.671961282225,
|
| 1035 |
+
"lat": 10.7550531135922
|
| 1036 |
+
},
|
| 1037 |
+
{
|
| 1038 |
+
"id": 210,
|
| 1039 |
+
"long": 106.677099291375,
|
| 1040 |
+
"lat": 10.7604640302358
|
| 1041 |
+
},
|
| 1042 |
+
{
|
| 1043 |
+
"id": 211,
|
| 1044 |
+
"long": 106.679091521875,
|
| 1045 |
+
"lat": 10.7625003037448
|
| 1046 |
+
},
|
| 1047 |
+
{
|
| 1048 |
+
"id": 212,
|
| 1049 |
+
"long": 106.68404317865,
|
| 1050 |
+
"lat": 10.7594742333703
|
| 1051 |
+
},
|
| 1052 |
+
{
|
| 1053 |
+
"id": 213,
|
| 1054 |
+
"long": 106.685228877125,
|
| 1055 |
+
"lat": 10.7564544006935
|
| 1056 |
+
},
|
| 1057 |
+
{
|
| 1058 |
+
"id": 214,
|
| 1059 |
+
"long": 106.68669091725,
|
| 1060 |
+
"lat": 10.7540297016644
|
| 1061 |
+
},
|
| 1062 |
+
{
|
| 1063 |
+
"id": 215,
|
| 1064 |
+
"long": 106.687783409875,
|
| 1065 |
+
"lat": 10.7506445563107
|
| 1066 |
+
},
|
| 1067 |
+
{
|
| 1068 |
+
"id": 216,
|
| 1069 |
+
"long": 106.658762118271,
|
| 1070 |
+
"lat": 10.752927565742
|
| 1071 |
+
},
|
| 1072 |
+
{
|
| 1073 |
+
"id": 217,
|
| 1074 |
+
"long": 106.657391115604,
|
| 1075 |
+
"lat": 10.7528855796117
|
| 1076 |
+
},
|
| 1077 |
+
{
|
| 1078 |
+
"id": 218,
|
| 1079 |
+
"long": 106.653567303479,
|
| 1080 |
+
"lat": 10.7526756489598
|
| 1081 |
+
},
|
| 1082 |
+
{
|
| 1083 |
+
"id": 219,
|
| 1084 |
+
"long": 106.650734255,
|
| 1085 |
+
"lat": 10.7532582065187
|
| 1086 |
+
},
|
| 1087 |
+
{
|
| 1088 |
+
"id": 220,
|
| 1089 |
+
"long": 106.661482751688,
|
| 1090 |
+
"lat": 10.7530010414702
|
| 1091 |
+
},
|
| 1092 |
+
{
|
| 1093 |
+
"id": 221,
|
| 1094 |
+
"long": 106.662885887229,
|
| 1095 |
+
"lat": 10.7532424617198
|
| 1096 |
+
},
|
| 1097 |
+
{
|
| 1098 |
+
"id": 222,
|
| 1099 |
+
"long": 106.664181913187,
|
| 1100 |
+
"lat": 10.7534523923717
|
| 1101 |
+
},
|
| 1102 |
+
{
|
| 1103 |
+
"id": 223,
|
| 1104 |
+
"long": 106.666602589771,
|
| 1105 |
+
"lat": 10.7539562259362
|
| 1106 |
+
},
|
| 1107 |
+
{
|
| 1108 |
+
"id": 224,
|
| 1109 |
+
"long": 106.67626705315,
|
| 1110 |
+
"lat": 10.7563504850208
|
| 1111 |
+
},
|
| 1112 |
+
{
|
| 1113 |
+
"id": 225,
|
| 1114 |
+
"long": 106.68077849125,
|
| 1115 |
+
"lat": 10.7582146692094
|
| 1116 |
+
},
|
| 1117 |
+
{
|
| 1118 |
+
"id": 226,
|
| 1119 |
+
"long": 106.68663950485,
|
| 1120 |
+
"lat": 10.7625223458807
|
| 1121 |
+
},
|
| 1122 |
+
{
|
| 1123 |
+
"id": 227,
|
| 1124 |
+
"long": 106.687089339775,
|
| 1125 |
+
"lat": 10.7633158627739
|
| 1126 |
+
},
|
| 1127 |
+
{
|
| 1128 |
+
"id": 228,
|
| 1129 |
+
"long": 106.688336095325,
|
| 1130 |
+
"lat": 10.7666788527878
|
| 1131 |
+
},
|
| 1132 |
+
{
|
| 1133 |
+
"id": 229,
|
| 1134 |
+
"long": 106.689762794975,
|
| 1135 |
+
"lat": 10.7686814853537
|
| 1136 |
+
},
|
| 1137 |
+
{
|
| 1138 |
+
"id": 230,
|
| 1139 |
+
"long": 106.693284558075,
|
| 1140 |
+
"lat": 10.7713516621082
|
| 1141 |
+
},
|
| 1142 |
+
{
|
| 1143 |
+
"id": 231,
|
| 1144 |
+
"long": 106.654421502406,
|
| 1145 |
+
"lat": 10.7519277710125
|
| 1146 |
+
},
|
| 1147 |
+
{
|
| 1148 |
+
"id": 232,
|
| 1149 |
+
"long": 106.658920104906,
|
| 1150 |
+
"lat": 10.7519592606103
|
| 1151 |
+
},
|
| 1152 |
+
{
|
| 1153 |
+
"id": 233,
|
| 1154 |
+
"long": 106.661482751688,
|
| 1155 |
+
"lat": 10.7520563535368
|
| 1156 |
+
},
|
| 1157 |
+
{
|
| 1158 |
+
"id": 234,
|
| 1159 |
+
"long": 106.664417554271,
|
| 1160 |
+
"lat": 10.752255787656
|
| 1161 |
+
},
|
| 1162 |
+
{
|
| 1163 |
+
"id": 235,
|
| 1164 |
+
"long": 106.656105800604,
|
| 1165 |
+
"lat": 10.751972381276
|
| 1166 |
+
},
|
| 1167 |
+
{
|
| 1168 |
+
"id": 236,
|
| 1169 |
+
"long": 106.657487514229,
|
| 1170 |
+
"lat": 10.751972381276
|
| 1171 |
+
},
|
| 1172 |
+
{
|
| 1173 |
+
"id": 237,
|
| 1174 |
+
"long": 106.659980489781,
|
| 1175 |
+
"lat": 10.751990750208
|
| 1176 |
+
},
|
| 1177 |
+
{
|
| 1178 |
+
"id": 238,
|
| 1179 |
+
"long": 106.666773965104,
|
| 1180 |
+
"lat": 10.7524657183079
|
| 1181 |
+
},
|
| 1182 |
+
{
|
| 1183 |
+
"id": 239,
|
| 1184 |
+
"long": 106.672065178521,
|
| 1185 |
+
"lat": 10.7529380622746
|
| 1186 |
+
},
|
| 1187 |
+
{
|
| 1188 |
+
"id": 240,
|
| 1189 |
+
"long": 106.674775022458,
|
| 1190 |
+
"lat": 10.7535888472954
|
| 1191 |
+
},
|
| 1192 |
+
{
|
| 1193 |
+
"id": 241,
|
| 1194 |
+
"long": 106.676885073042,
|
| 1195 |
+
"lat": 10.7541766531207
|
| 1196 |
+
},
|
| 1197 |
+
{
|
| 1198 |
+
"id": 242,
|
| 1199 |
+
"long": 106.681415790792,
|
| 1200 |
+
"lat": 10.7553732578363
|
| 1201 |
+
},
|
| 1202 |
+
{
|
| 1203 |
+
"id": 243,
|
| 1204 |
+
"long": 106.683686505125,
|
| 1205 |
+
"lat": 10.7559820567268
|
| 1206 |
+
},
|
| 1207 |
+
{
|
| 1208 |
+
"id": 244,
|
| 1209 |
+
"long": 106.687598645844,
|
| 1210 |
+
"lat": 10.759028667337
|
| 1211 |
+
},
|
| 1212 |
+
{
|
| 1213 |
+
"id": 245,
|
| 1214 |
+
"long": 106.689783681344,
|
| 1215 |
+
"lat": 10.7615792573509
|
| 1216 |
+
},
|
| 1217 |
+
{
|
| 1218 |
+
"id": 246,
|
| 1219 |
+
"long": 106.692434643531,
|
| 1220 |
+
"lat": 10.7646179232316
|
| 1221 |
+
},
|
| 1222 |
+
{
|
| 1223 |
+
"id": 247,
|
| 1224 |
+
"long": 106.694314416719,
|
| 1225 |
+
"lat": 10.7668063789182
|
| 1226 |
+
},
|
| 1227 |
+
{
|
| 1228 |
+
"id": 248,
|
| 1229 |
+
"long": 106.695406934469,
|
| 1230 |
+
"lat": 10.7680029203883
|
| 1231 |
+
},
|
| 1232 |
+
{
|
| 1233 |
+
"id": 249,
|
| 1234 |
+
"long": 106.634458953812,
|
| 1235 |
+
"lat": 10.7538092744799
|
| 1236 |
+
},
|
| 1237 |
+
{
|
| 1238 |
+
"id": 250,
|
| 1239 |
+
"long": 106.643175046156,
|
| 1240 |
+
"lat": 10.7541005532594
|
| 1241 |
+
},
|
| 1242 |
+
{
|
| 1243 |
+
"id": 251,
|
| 1244 |
+
"long": 106.6480351435,
|
| 1245 |
+
"lat": 10.7535416128988
|
| 1246 |
+
},
|
| 1247 |
+
{
|
| 1248 |
+
"id": 252,
|
| 1249 |
+
"long": 106.653770811688,
|
| 1250 |
+
"lat": 10.7535731024965
|
| 1251 |
+
},
|
| 1252 |
+
{
|
| 1253 |
+
"id": 253,
|
| 1254 |
+
"long": 106.655056126687,
|
| 1255 |
+
"lat": 10.753793529681
|
| 1256 |
+
},
|
| 1257 |
+
{
|
| 1258 |
+
"id": 254,
|
| 1259 |
+
"long": 106.658462211437,
|
| 1260 |
+
"lat": 10.7543760872399
|
| 1261 |
+
},
|
| 1262 |
+
{
|
| 1263 |
+
"id": 255,
|
| 1264 |
+
"long": 106.656132578,
|
| 1265 |
+
"lat": 10.7540139568655
|
| 1266 |
+
},
|
| 1267 |
+
{
|
| 1268 |
+
"id": 256,
|
| 1269 |
+
"long": 106.660325918187,
|
| 1270 |
+
"lat": 10.7548799208044
|
| 1271 |
+
},
|
| 1272 |
+
{
|
| 1273 |
+
"id": 257,
|
| 1274 |
+
"long": 106.661547017437,
|
| 1275 |
+
"lat": 10.7551108445215
|
| 1276 |
+
},
|
| 1277 |
+
{
|
| 1278 |
+
"id": 258,
|
| 1279 |
+
"long": 106.662437098075,
|
| 1280 |
+
"lat": 10.7552609449376
|
| 1281 |
+
},
|
| 1282 |
+
{
|
| 1283 |
+
"id": 259,
|
| 1284 |
+
"long": 106.663432146104,
|
| 1285 |
+
"lat": 10.7555097127601
|
| 1286 |
+
},
|
| 1287 |
+
{
|
| 1288 |
+
"id": 260,
|
| 1289 |
+
"long": 106.663774896771,
|
| 1290 |
+
"lat": 10.755562195423
|
| 1291 |
+
},
|
| 1292 |
+
{
|
| 1293 |
+
"id": 261,
|
| 1294 |
+
"long": 106.642339591406,
|
| 1295 |
+
"lat": 10.7496919708044
|
| 1296 |
+
},
|
| 1297 |
+
{
|
| 1298 |
+
"id": 262,
|
| 1299 |
+
"long": 106.643279478,
|
| 1300 |
+
"lat": 10.7546437488211
|
| 1301 |
+
},
|
| 1302 |
+
{
|
| 1303 |
+
"id": 263,
|
| 1304 |
+
"long": 106.643938201938,
|
| 1305 |
+
"lat": 10.758249307767
|
| 1306 |
+
},
|
| 1307 |
+
{
|
| 1308 |
+
"id": 264,
|
| 1309 |
+
"long": 106.642741252344,
|
| 1310 |
+
"lat": 10.7467948512483
|
| 1311 |
+
},
|
| 1312 |
+
{
|
| 1313 |
+
"id": 265,
|
| 1314 |
+
"long": 106.642917983156,
|
| 1315 |
+
"lat": 10.7458501383495
|
| 1316 |
+
},
|
| 1317 |
+
{
|
| 1318 |
+
"id": 266,
|
| 1319 |
+
"long": 106.643271444781,
|
| 1320 |
+
"lat": 10.7427797803571
|
| 1321 |
+
},
|
| 1322 |
+
{
|
| 1323 |
+
"id": 267,
|
| 1324 |
+
"long": 106.643881969406,
|
| 1325 |
+
"lat": 10.7400400586512
|
| 1326 |
+
},
|
| 1327 |
+
{
|
| 1328 |
+
"id": 268,
|
| 1329 |
+
"long": 106.644412161844,
|
| 1330 |
+
"lat": 10.743425346966
|
| 1331 |
+
},
|
| 1332 |
+
{
|
| 1333 |
+
"id": 269,
|
| 1334 |
+
"long": 106.645729609719,
|
| 1335 |
+
"lat": 10.7411894821255
|
| 1336 |
+
},
|
| 1337 |
+
{
|
| 1338 |
+
"id": 270,
|
| 1339 |
+
"long": 106.640009957969,
|
| 1340 |
+
"lat": 10.7492038691401
|
| 1341 |
+
},
|
| 1342 |
+
{
|
| 1343 |
+
"id": 271,
|
| 1344 |
+
"long": 106.635189976719,
|
| 1345 |
+
"lat": 10.7479757423717
|
| 1346 |
+
},
|
| 1347 |
+
{
|
| 1348 |
+
"id": 272,
|
| 1349 |
+
"long": 106.631446496781,
|
| 1350 |
+
"lat": 10.7470782651179
|
| 1351 |
+
},
|
| 1352 |
+
{
|
| 1353 |
+
"id": 273,
|
| 1354 |
+
"long": 106.628859800344,
|
| 1355 |
+
"lat": 10.7464169660888
|
| 1356 |
+
},
|
| 1357 |
+
{
|
| 1358 |
+
"id": 274,
|
| 1359 |
+
"long": 106.625630446406,
|
| 1360 |
+
"lat": 10.7456139601248
|
| 1361 |
+
},
|
| 1362 |
+
{
|
| 1363 |
+
"id": 275,
|
| 1364 |
+
"long": 106.623750673219,
|
| 1365 |
+
"lat": 10.7454407627601
|
| 1366 |
+
},
|
| 1367 |
+
{
|
| 1368 |
+
"id": 276,
|
| 1369 |
+
"long": 106.665587726469,
|
| 1370 |
+
"lat": 10.7591703667822
|
| 1371 |
+
},
|
| 1372 |
+
{
|
| 1373 |
+
"id": 277,
|
| 1374 |
+
"long": 106.671069059396,
|
| 1375 |
+
"lat": 10.7603380751734
|
| 1376 |
+
},
|
| 1377 |
+
{
|
| 1378 |
+
"id": 278,
|
| 1379 |
+
"long": 106.662760033469,
|
| 1380 |
+
"lat": 10.7585878209431
|
| 1381 |
+
},
|
| 1382 |
+
{
|
| 1383 |
+
"id": 279,
|
| 1384 |
+
"long": 106.661522917781,
|
| 1385 |
+
"lat": 10.7583516489598
|
| 1386 |
+
},
|
| 1387 |
+
{
|
| 1388 |
+
"id": 280,
|
| 1389 |
+
"long": 106.660623147281,
|
| 1390 |
+
"lat": 10.7581627113731
|
| 1391 |
+
},
|
| 1392 |
+
{
|
| 1393 |
+
"id": 281,
|
| 1394 |
+
"long": 106.659498496656,
|
| 1395 |
+
"lat": 10.7579422841886
|
| 1396 |
+
},
|
| 1397 |
+
{
|
| 1398 |
+
"id": 282,
|
| 1399 |
+
"long": 106.658277447406,
|
| 1400 |
+
"lat": 10.7576746226075
|
| 1401 |
+
},
|
| 1402 |
+
{
|
| 1403 |
+
"id": 283,
|
| 1404 |
+
"long": 106.657088531031,
|
| 1405 |
+
"lat": 10.7574384506241
|
| 1406 |
+
},
|
| 1407 |
+
{
|
| 1408 |
+
"id": 284,
|
| 1409 |
+
"long": 106.656012079719,
|
| 1410 |
+
"lat": 10.7572337682386
|
| 1411 |
+
},
|
| 1412 |
+
{
|
| 1413 |
+
"id": 285,
|
| 1414 |
+
"long": 106.653666379844,
|
| 1415 |
+
"lat": 10.7567771690707
|
| 1416 |
+
},
|
| 1417 |
+
{
|
| 1418 |
+
"id": 286,
|
| 1419 |
+
"long": 106.651127882719,
|
| 1420 |
+
"lat": 10.7562890803051
|
| 1421 |
+
},
|
| 1422 |
+
{
|
| 1423 |
+
"id": 287,
|
| 1424 |
+
"long": 106.651401012156,
|
| 1425 |
+
"lat": 10.7590601561026
|
| 1426 |
+
},
|
| 1427 |
+
{
|
| 1428 |
+
"id": 288,
|
| 1429 |
+
"long": 106.650469158781,
|
| 1430 |
+
"lat": 10.7509673332178
|
| 1431 |
+
},
|
| 1432 |
+
{
|
| 1433 |
+
"id": 289,
|
| 1434 |
+
"long": 106.601410291875,
|
| 1435 |
+
"lat": 10.7236563842233
|
| 1436 |
+
},
|
| 1437 |
+
{
|
| 1438 |
+
"id": 290,
|
| 1439 |
+
"long": 106.604157652687,
|
| 1440 |
+
"lat": 10.726034084466
|
| 1441 |
+
},
|
| 1442 |
+
{
|
| 1443 |
+
"id": 291,
|
| 1444 |
+
"long": 106.607242408687,
|
| 1445 |
+
"lat": 10.728081111165
|
| 1446 |
+
},
|
| 1447 |
+
{
|
| 1448 |
+
"id": 292,
|
| 1449 |
+
"long": 106.6102468825,
|
| 1450 |
+
"lat": 10.7301596355062
|
| 1451 |
+
},
|
| 1452 |
+
{
|
| 1453 |
+
"id": 293,
|
| 1454 |
+
"long": 106.610985938625,
|
| 1455 |
+
"lat": 10.7312775974688
|
| 1456 |
+
},
|
| 1457 |
+
{
|
| 1458 |
+
"id": 294,
|
| 1459 |
+
"long": 106.611933858437,
|
| 1460 |
+
"lat": 10.7328049539528
|
| 1461 |
+
},
|
| 1462 |
+
{
|
| 1463 |
+
"id": 295,
|
| 1464 |
+
"long": 106.61346017,
|
| 1465 |
+
"lat": 10.7353557967406
|
| 1466 |
+
},
|
| 1467 |
+
{
|
| 1468 |
+
"id": 296,
|
| 1469 |
+
"long": 106.614986481563,
|
| 1470 |
+
"lat": 10.7379537763176
|
| 1471 |
+
},
|
| 1472 |
+
{
|
| 1473 |
+
"id": 297,
|
| 1474 |
+
"long": 106.618296167688,
|
| 1475 |
+
"lat": 10.7410241540915
|
| 1476 |
+
},
|
| 1477 |
+
{
|
| 1478 |
+
"id": 298,
|
| 1479 |
+
"long": 106.628851767125,
|
| 1480 |
+
"lat": 10.7496840981969
|
| 1481 |
+
},
|
| 1482 |
+
{
|
| 1483 |
+
"id": 300,
|
| 1484 |
+
"long": 106.656807368375,
|
| 1485 |
+
"lat": 10.775073503828
|
| 1486 |
+
},
|
| 1487 |
+
{
|
| 1488 |
+
"id": 301,
|
| 1489 |
+
"long": 106.660226306275,
|
| 1490 |
+
"lat": 10.7628561267961
|
| 1491 |
+
},
|
| 1492 |
+
{
|
| 1493 |
+
"id": 302,
|
| 1494 |
+
"long": 106.660509075575,
|
| 1495 |
+
"lat": 10.761961845853
|
| 1496 |
+
},
|
| 1497 |
+
{
|
| 1498 |
+
"id": 303,
|
| 1499 |
+
"long": 106.660766138575,
|
| 1500 |
+
"lat": 10.7610423738974
|
| 1501 |
+
},
|
| 1502 |
+
{
|
| 1503 |
+
"id": 304,
|
| 1504 |
+
"long": 106.661061811025,
|
| 1505 |
+
"lat": 10.760135497448
|
| 1506 |
+
},
|
| 1507 |
+
{
|
| 1508 |
+
"id": 305,
|
| 1509 |
+
"long": 106.661331727175,
|
| 1510 |
+
"lat": 10.7592160254924
|
| 1511 |
+
},
|
| 1512 |
+
{
|
| 1513 |
+
"id": 306,
|
| 1514 |
+
"long": 106.655316402975,
|
| 1515 |
+
"lat": 10.7804640955617
|
| 1516 |
+
},
|
| 1517 |
+
{
|
| 1518 |
+
"id": 307,
|
| 1519 |
+
"long": 106.654429535625,
|
| 1520 |
+
"lat": 10.7836127374133
|
| 1521 |
+
},
|
| 1522 |
+
{
|
| 1523 |
+
"id": 308,
|
| 1524 |
+
"long": 106.654223885225,
|
| 1525 |
+
"lat": 10.7843810074549
|
| 1526 |
+
},
|
| 1527 |
+
{
|
| 1528 |
+
"id": 309,
|
| 1529 |
+
"long": 106.653851143875,
|
| 1530 |
+
"lat": 10.7856152773578
|
| 1531 |
+
},
|
| 1532 |
+
{
|
| 1533 |
+
"id": 310,
|
| 1534 |
+
"long": 106.653542668275,
|
| 1535 |
+
"lat": 10.7866858175797
|
| 1536 |
+
},
|
| 1537 |
+
{
|
| 1538 |
+
"id": 311,
|
| 1539 |
+
"long": 106.655701997475,
|
| 1540 |
+
"lat": 10.7953255896325
|
| 1541 |
+
},
|
| 1542 |
+
{
|
| 1543 |
+
"id": 312,
|
| 1544 |
+
"long": 106.658401158975,
|
| 1545 |
+
"lat": 10.7978821391609
|
| 1546 |
+
},
|
| 1547 |
+
{
|
| 1548 |
+
"id": 313,
|
| 1549 |
+
"long": 106.660611900775,
|
| 1550 |
+
"lat": 10.7999853246533
|
| 1551 |
+
},
|
| 1552 |
+
{
|
| 1553 |
+
"id": 314,
|
| 1554 |
+
"long": 106.665470441475,
|
| 1555 |
+
"lat": 10.8003253606311
|
| 1556 |
+
},
|
| 1557 |
+
{
|
| 1558 |
+
"id": 315,
|
| 1559 |
+
"long": 106.667552651775,
|
| 1560 |
+
"lat": 10.8001742335298
|
| 1561 |
+
},
|
| 1562 |
+
{
|
| 1563 |
+
"id": 316,
|
| 1564 |
+
"long": 106.673015240525,
|
| 1565 |
+
"lat": 10.7996830704508
|
| 1566 |
+
},
|
| 1567 |
+
{
|
| 1568 |
+
"id": 317,
|
| 1569 |
+
"long": 106.67491747765,
|
| 1570 |
+
"lat": 10.7995697251248
|
| 1571 |
+
},
|
| 1572 |
+
{
|
| 1573 |
+
"id": 318,
|
| 1574 |
+
"long": 106.67752665695,
|
| 1575 |
+
"lat": 10.7993556283981
|
| 1576 |
+
},
|
| 1577 |
+
{
|
| 1578 |
+
"id": 319,
|
| 1579 |
+
"long": 106.68027722035,
|
| 1580 |
+
"lat": 10.7991415316713
|
| 1581 |
+
},
|
| 1582 |
+
{
|
| 1583 |
+
"id": 320,
|
| 1584 |
+
"long": 106.660615114062,
|
| 1585 |
+
"lat": 10.7889890533807
|
| 1586 |
+
},
|
| 1587 |
+
{
|
| 1588 |
+
"id": 321,
|
| 1589 |
+
"long": 106.657176896438,
|
| 1590 |
+
"lat": 10.7907837853502
|
| 1591 |
+
},
|
| 1592 |
+
{
|
| 1593 |
+
"id": 322,
|
| 1594 |
+
"long": 106.66647941375,
|
| 1595 |
+
"lat": 10.7858089191921
|
| 1596 |
+
},
|
| 1597 |
+
{
|
| 1598 |
+
"id": 323,
|
| 1599 |
+
"long": 106.67711535775,
|
| 1600 |
+
"lat": 10.7801728398752
|
| 1601 |
+
},
|
| 1602 |
+
{
|
| 1603 |
+
"id": 324,
|
| 1604 |
+
"long": 106.678641663375,
|
| 1605 |
+
"lat": 10.7793384316921
|
| 1606 |
+
},
|
| 1607 |
+
{
|
| 1608 |
+
"id": 325,
|
| 1609 |
+
"long": 106.6837025715,
|
| 1610 |
+
"lat": 10.776662028086
|
| 1611 |
+
},
|
| 1612 |
+
{
|
| 1613 |
+
"id": 326,
|
| 1614 |
+
"long": 106.686835490062,
|
| 1615 |
+
"lat": 10.7749932117198
|
| 1616 |
+
},
|
| 1617 |
+
{
|
| 1618 |
+
"id": 327,
|
| 1619 |
+
"long": 106.688281469437,
|
| 1620 |
+
"lat": 10.7742375212899
|
| 1621 |
+
},
|
| 1622 |
+
{
|
| 1623 |
+
"id": 328,
|
| 1624 |
+
"long": 106.628867833563,
|
| 1625 |
+
"lat": 10.7820384135576
|
| 1626 |
+
},
|
| 1627 |
+
{
|
| 1628 |
+
"id": 329,
|
| 1629 |
+
"long": 106.621429123,
|
| 1630 |
+
"lat": 10.7843526696255
|
| 1631 |
+
},
|
| 1632 |
+
{
|
| 1633 |
+
"id": 330,
|
| 1634 |
+
"long": 106.622023581188,
|
| 1635 |
+
"lat": 10.7860844258669
|
| 1636 |
+
},
|
| 1637 |
+
{
|
| 1638 |
+
"id": 331,
|
| 1639 |
+
"long": 106.623212447563,
|
| 1640 |
+
"lat": 10.7897998589459
|
| 1641 |
+
},
|
| 1642 |
+
{
|
| 1643 |
+
"id": 332,
|
| 1644 |
+
"long": 106.620127741563,
|
| 1645 |
+
"lat": 10.7804168649098
|
| 1646 |
+
},
|
| 1647 |
+
{
|
| 1648 |
+
"id": 333,
|
| 1649 |
+
"long": 106.627036259688,
|
| 1650 |
+
"lat": 10.7717421124827
|
| 1651 |
+
},
|
| 1652 |
+
{
|
| 1653 |
+
"id": 334,
|
| 1654 |
+
"long": 106.632643446375,
|
| 1655 |
+
"lat": 10.755808863939
|
| 1656 |
+
},
|
| 1657 |
+
{
|
| 1658 |
+
"id": 335,
|
| 1659 |
+
"long": 106.631872257375,
|
| 1660 |
+
"lat": 10.7543760872399
|
| 1661 |
+
},
|
| 1662 |
+
{
|
| 1663 |
+
"id": 336,
|
| 1664 |
+
"long": 106.631534862188,
|
| 1665 |
+
"lat": 10.7534943785021
|
| 1666 |
+
},
|
| 1667 |
+
{
|
| 1668 |
+
"id": 337,
|
| 1669 |
+
"long": 106.632386383375,
|
| 1670 |
+
"lat": 10.7524552217753
|
| 1671 |
+
}
|
| 1672 |
+
]
|
backend/export_streets.csv
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
id,name
|
| 2 |
+
1,Võ Văn Kiệt
|
| 3 |
+
2,Quốc lộ 13
|
| 4 |
+
3,Đinh Bộ Lĩnh
|
| 5 |
+
4,Xô Viết Nghệ Tĩnh
|
| 6 |
+
5,Phạm Văn Đồng
|
| 7 |
+
6,Nguyễn Xí
|
| 8 |
+
7,Chu Văn An
|
| 9 |
+
8,Xa lộ Hà Nội
|
| 10 |
+
9,Võ Nguyên Giáp
|
| 11 |
+
10,Mai Chí Thọ
|
| 12 |
+
11,Nguyễn Thị Định
|
| 13 |
+
12,Đồng Văn Cống
|
| 14 |
+
13,Nguyễn Duy Trinh
|
| 15 |
+
14,Vành đai 2
|
| 16 |
+
15,Liên Phường
|
| 17 |
+
16,Võ Chí Công
|
| 18 |
+
17,Đỗ Xuân Hợp
|
| 19 |
+
18,Lã Xuân Oai
|
| 20 |
+
19,Nguyễn Xiển
|
| 21 |
+
20,Quốc lộ 22
|
| 22 |
+
21,Trường Chinh
|
| 23 |
+
22,Cộng Hòa
|
| 24 |
+
23,Hoàng Văn Thụ
|
| 25 |
+
24,Quang Trung
|
| 26 |
+
25,Tô Ký
|
| 27 |
+
26,Nguyễn Ảnh Thủ
|
| 28 |
+
27,Phan Văn Hớn
|
| 29 |
+
28,Lý Thái Tổ
|
| 30 |
+
29,Quốc lộ 1A Bình Chánh
|
| 31 |
+
30,Kinh Dương Vương
|
| 32 |
+
31,An Dương Vương
|
| 33 |
+
32,Nguyễn Văn Linh
|
| 34 |
+
33,Hồng Bàng
|
| 35 |
+
34,Nguyễn Hữu Thọ
|
| 36 |
+
35,Huỳnh Tấn Phát
|
| 37 |
+
36,Nguyễn Chí Thanh
|
| 38 |
+
37,Nguyễn Tất Thành
|
| 39 |
+
38,Tôn Đức Thắng
|
| 40 |
+
39,Khánh Hội
|
| 41 |
+
40,Điện Biên Phủ
|
| 42 |
+
41,Nguyễn Thị Minh Khai
|
| 43 |
+
42,Võ Thị Sáu
|
| 44 |
+
43,Nam Kỳ Khởi Nghĩa
|
| 45 |
+
44,Hai Bà Trưng
|
| 46 |
+
45,Pasteur
|
| 47 |
+
46,Cách Mạng Tháng Tám
|
| 48 |
+
47,Lý Thường Kiệt
|
| 49 |
+
48,Ba Tháng Hai
|
| 50 |
+
49,Nguyễn Văn Cừ
|
| 51 |
+
50,Trần Hưng Đạo
|
| 52 |
+
51,Tân Kỳ Tân Quý
|
| 53 |
+
52,Tân Sơn Nhì
|
| 54 |
+
53,Lê Tấn Quốc
|
| 55 |
+
54,Lê Trọng Tấn
|
| 56 |
+
55,Nguyễn Cửu Đàm
|
| 57 |
+
56,Nguyễn Quý Anh
|
| 58 |
+
57,Cầu Xéo
|
| 59 |
+
58,Diệp Minh Châu
|
| 60 |
+
59,Trương Vĩnh Ký
|
| 61 |
+
60,Gò Dầu
|
| 62 |
+
61,Độc Lập
|
| 63 |
+
62,Luỹ Bán Bích
|
| 64 |
+
63,Vườn Lài
|
| 65 |
+
64,Thống Nhất
|
| 66 |
+
65,Hồng Lạc
|
| 67 |
+
66,Âu Cơ
|
| 68 |
+
67,Thoại Ngọc Hầu
|
| 69 |
+
68,Đồng Đen
|
| 70 |
+
69,Lạc Long Quân
|
| 71 |
+
70,Phan Huy Ích
|
| 72 |
+
71,Trương Công Định
|
| 73 |
+
72,Hoàng Hoa Thám
|
| 74 |
+
73,C12 - Sân Bay
|
| 75 |
+
74,Bình Thới
|
| 76 |
+
75,Lãnh Binh Thăng
|
| 77 |
+
76,Ông ích Khiêm
|
| 78 |
+
77,Hoà Bình
|
| 79 |
+
78,Lê Đại Hành
|
| 80 |
+
79,Nguyễn Thị Nhỏ
|
| 81 |
+
80,Thiên Phước
|
| 82 |
+
81,Lữ Gia
|
| 83 |
+
82,Tô Hiến Thành
|
| 84 |
+
83,Thành Thái
|
| 85 |
+
84,Bắc Hải
|
| 86 |
+
85,Sư Vạn Hạnh
|
| 87 |
+
86,Đồng Nai
|
| 88 |
+
87,Hồ Bá Kiện
|
| 89 |
+
88,Nguyễn Tri Phương
|
| 90 |
+
89,3 Tháng 2
|
| 91 |
+
90,Lê Hồng Phong
|
| 92 |
+
91,Cao Thắng
|
| 93 |
+
92,Ngô Gia Tự
|
| 94 |
+
93,Hùng Vương
|
| 95 |
+
94,Trần Phú
|
| 96 |
+
95,Nguyễn Trãi
|
| 97 |
+
96,Minh Phụng
|
| 98 |
+
97,Bình Tiên
|
| 99 |
+
98,Phạm Phú Thứ
|
| 100 |
+
99,Hậu Giang
|
| 101 |
+
100,Nguyễn Thị Nhỏ Quận 6
|
| 102 |
+
101,Nguyễn Sơn
|
| 103 |
+
102,Văn Cao
|
| 104 |
+
103,Đặng Nguyên Cẩn
|
backend/export_streets.json
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": 1,
|
| 4 |
+
"name": "Võ Văn Kiệt"
|
| 5 |
+
},
|
| 6 |
+
{
|
| 7 |
+
"id": 2,
|
| 8 |
+
"name": "Quốc lộ 13"
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"id": 3,
|
| 12 |
+
"name": "Đinh Bộ Lĩnh"
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"id": 4,
|
| 16 |
+
"name": "Xô Viết Nghệ Tĩnh"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"id": 5,
|
| 20 |
+
"name": "Phạm Văn Đồng"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"id": 6,
|
| 24 |
+
"name": "Nguyễn Xí"
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"id": 7,
|
| 28 |
+
"name": "Chu Văn An"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"id": 8,
|
| 32 |
+
"name": "Xa lộ Hà Nội"
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": 9,
|
| 36 |
+
"name": "Võ Nguyên Giáp"
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"id": 10,
|
| 40 |
+
"name": "Mai Chí Thọ"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"id": 11,
|
| 44 |
+
"name": "Nguyễn Thị Định"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"id": 12,
|
| 48 |
+
"name": "Đồng Văn Cống"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"id": 13,
|
| 52 |
+
"name": "Nguyễn Duy Trinh"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"id": 14,
|
| 56 |
+
"name": "Vành đai 2"
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"id": 15,
|
| 60 |
+
"name": "Liên Phường"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"id": 16,
|
| 64 |
+
"name": "Võ Chí Công"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"id": 17,
|
| 68 |
+
"name": "Đỗ Xuân Hợp"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"id": 18,
|
| 72 |
+
"name": "Lã Xuân Oai"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"id": 19,
|
| 76 |
+
"name": "Nguyễn Xiển"
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"id": 20,
|
| 80 |
+
"name": "Quốc lộ 22"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": 21,
|
| 84 |
+
"name": "Trường Chinh"
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"id": 22,
|
| 88 |
+
"name": "Cộng Hòa"
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"id": 23,
|
| 92 |
+
"name": "Hoàng Văn Thụ"
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"id": 24,
|
| 96 |
+
"name": "Quang Trung"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"id": 25,
|
| 100 |
+
"name": "Tô Ký"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"id": 26,
|
| 104 |
+
"name": "Nguyễn Ảnh Thủ"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"id": 27,
|
| 108 |
+
"name": "Phan Văn Hớn"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"id": 28,
|
| 112 |
+
"name": "Lý Thái Tổ"
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"id": 29,
|
| 116 |
+
"name": "Quốc lộ 1A Bình Chánh"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"id": 30,
|
| 120 |
+
"name": "Kinh Dương Vương"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"id": 31,
|
| 124 |
+
"name": "An Dương Vương"
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"id": 32,
|
| 128 |
+
"name": "Nguyễn Văn Linh"
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"id": 33,
|
| 132 |
+
"name": "Hồng Bàng"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"id": 34,
|
| 136 |
+
"name": "Nguyễn Hữu Thọ"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"id": 35,
|
| 140 |
+
"name": "Huỳnh Tấn Phát"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"id": 36,
|
| 144 |
+
"name": "Nguyễn Chí Thanh"
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"id": 37,
|
| 148 |
+
"name": "Nguyễn Tất Thành"
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"id": 38,
|
| 152 |
+
"name": "Tôn Đức Thắng"
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"id": 39,
|
| 156 |
+
"name": "Khánh Hội"
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"id": 40,
|
| 160 |
+
"name": "Điện Biên Phủ"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"id": 41,
|
| 164 |
+
"name": "Nguyễn Thị Minh Khai"
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"id": 42,
|
| 168 |
+
"name": "Võ Thị Sáu"
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"id": 43,
|
| 172 |
+
"name": "Nam Kỳ Khởi Nghĩa"
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"id": 44,
|
| 176 |
+
"name": "Hai Bà Trưng"
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"id": 45,
|
| 180 |
+
"name": "Pasteur"
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"id": 46,
|
| 184 |
+
"name": "Cách Mạng Tháng Tám"
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"id": 47,
|
| 188 |
+
"name": "Lý Thường Kiệt"
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"id": 48,
|
| 192 |
+
"name": "Ba Tháng Hai"
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"id": 49,
|
| 196 |
+
"name": "Nguyễn Văn Cừ"
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"id": 50,
|
| 200 |
+
"name": "Trần Hưng Đạo"
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"id": 51,
|
| 204 |
+
"name": "Tân Kỳ Tân Quý"
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"id": 52,
|
| 208 |
+
"name": "Tân Sơn Nhì"
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"id": 53,
|
| 212 |
+
"name": "Lê Tấn Quốc"
|
| 213 |
+
},
|
| 214 |
+
{
|
| 215 |
+
"id": 54,
|
| 216 |
+
"name": "Lê Trọng Tấn"
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"id": 55,
|
| 220 |
+
"name": "Nguyễn Cửu Đàm"
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"id": 56,
|
| 224 |
+
"name": "Nguyễn Quý Anh"
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"id": 57,
|
| 228 |
+
"name": "Cầu Xéo"
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"id": 58,
|
| 232 |
+
"name": "Diệp Minh Châu"
|
| 233 |
+
},
|
| 234 |
+
{
|
| 235 |
+
"id": 59,
|
| 236 |
+
"name": "Trương Vĩnh Ký"
|
| 237 |
+
},
|
| 238 |
+
{
|
| 239 |
+
"id": 60,
|
| 240 |
+
"name": "Gò Dầu"
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"id": 61,
|
| 244 |
+
"name": "Độc Lập"
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"id": 62,
|
| 248 |
+
"name": "Luỹ Bán Bích"
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"id": 63,
|
| 252 |
+
"name": "Vườn Lài"
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"id": 64,
|
| 256 |
+
"name": "Thống Nhất"
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
"id": 65,
|
| 260 |
+
"name": "Hồng Lạc"
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"id": 66,
|
| 264 |
+
"name": "Âu Cơ"
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"id": 67,
|
| 268 |
+
"name": "Thoại Ngọc Hầu"
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"id": 68,
|
| 272 |
+
"name": "Đồng Đen"
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
"id": 69,
|
| 276 |
+
"name": "Lạc Long Quân"
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"id": 70,
|
| 280 |
+
"name": "Phan Huy Ích"
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"id": 71,
|
| 284 |
+
"name": "Trương Công Định"
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"id": 72,
|
| 288 |
+
"name": "Hoàng Hoa Thám"
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"id": 73,
|
| 292 |
+
"name": "C12 - Sân Bay"
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"id": 74,
|
| 296 |
+
"name": "Bình Thới"
|
| 297 |
+
},
|
| 298 |
+
{
|
| 299 |
+
"id": 75,
|
| 300 |
+
"name": "Lãnh Binh Thăng"
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"id": 76,
|
| 304 |
+
"name": "Ông ích Khiêm"
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"id": 77,
|
| 308 |
+
"name": "Hoà Bình"
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"id": 78,
|
| 312 |
+
"name": "Lê Đại Hành"
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"id": 79,
|
| 316 |
+
"name": "Nguyễn Thị Nhỏ"
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"id": 80,
|
| 320 |
+
"name": "Thiên Phước"
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"id": 81,
|
| 324 |
+
"name": "Lữ Gia"
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"id": 82,
|
| 328 |
+
"name": "Tô Hiến Thành"
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"id": 83,
|
| 332 |
+
"name": "Thành Thái"
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"id": 84,
|
| 336 |
+
"name": "Bắc Hải"
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"id": 85,
|
| 340 |
+
"name": "Sư Vạn Hạnh"
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"id": 86,
|
| 344 |
+
"name": "Đồng Nai"
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"id": 87,
|
| 348 |
+
"name": "Hồ Bá Kiện"
|
| 349 |
+
},
|
| 350 |
+
{
|
| 351 |
+
"id": 88,
|
| 352 |
+
"name": "Nguyễn Tri Phương"
|
| 353 |
+
},
|
| 354 |
+
{
|
| 355 |
+
"id": 89,
|
| 356 |
+
"name": "3 Tháng 2"
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"id": 90,
|
| 360 |
+
"name": "Lê Hồng Phong"
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"id": 91,
|
| 364 |
+
"name": "Cao Thắng"
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"id": 92,
|
| 368 |
+
"name": "Ngô Gia Tự"
|
| 369 |
+
},
|
| 370 |
+
{
|
| 371 |
+
"id": 93,
|
| 372 |
+
"name": "Hùng Vương"
|
| 373 |
+
},
|
| 374 |
+
{
|
| 375 |
+
"id": 94,
|
| 376 |
+
"name": "Trần Phú"
|
| 377 |
+
},
|
| 378 |
+
{
|
| 379 |
+
"id": 95,
|
| 380 |
+
"name": "Nguyễn Trãi"
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"id": 96,
|
| 384 |
+
"name": "Minh Phụng"
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"id": 97,
|
| 388 |
+
"name": "Bình Tiên"
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"id": 98,
|
| 392 |
+
"name": "Phạm Phú Thứ"
|
| 393 |
+
},
|
| 394 |
+
{
|
| 395 |
+
"id": 99,
|
| 396 |
+
"name": "Hậu Giang"
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"id": 100,
|
| 400 |
+
"name": "Nguyễn Thị Nhỏ Quận 6"
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"id": 101,
|
| 404 |
+
"name": "Nguyễn Sơn"
|
| 405 |
+
},
|
| 406 |
+
{
|
| 407 |
+
"id": 102,
|
| 408 |
+
"name": "Văn Cao"
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
"id": 103,
|
| 412 |
+
"name": "Đặng Nguyên Cẩn"
|
| 413 |
+
}
|
| 414 |
+
]
|
backend/main.py
CHANGED
|
@@ -8,7 +8,7 @@ from fastapi import FastAPI, HTTPException, Query, Body
|
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
from starlette.middleware.base import BaseHTTPMiddleware
|
| 11 |
-
from starlette.responses import JSONResponse
|
| 12 |
from typing import Optional, List
|
| 13 |
import logging
|
| 14 |
|
|
@@ -20,6 +20,7 @@ from models import (
|
|
| 20 |
District, DistrictCreate, DistrictUpdate,
|
| 21 |
Street, StreetCreate, StreetUpdate,
|
| 22 |
Position, PositionCreate, PositionUpdate,
|
|
|
|
| 23 |
)
|
| 24 |
from services.category_service import CategoryService
|
| 25 |
from services.intent_service import IntentService
|
|
@@ -90,6 +91,42 @@ async def health_check():
|
|
| 90 |
raise HTTPException(status_code=500, detail="Database connection failed")
|
| 91 |
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
# ==================== CATEGORIES ENDPOINTS ====================
|
| 94 |
|
| 95 |
@app.get("/api/categories", response_model=List[Category])
|
|
@@ -364,14 +401,47 @@ async def search_streets(payload: dict = Body(...)):
|
|
| 364 |
raise HTTPException(status_code=500, detail=str(e))
|
| 365 |
|
| 366 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
@app.post("/api/streets", response_model=Street)
|
| 368 |
async def create_street(street: StreetCreate):
|
| 369 |
-
"""Create a new street"""
|
| 370 |
try:
|
| 371 |
return await MapsService.create_street(street)
|
| 372 |
except Exception as e:
|
| 373 |
-
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
|
| 377 |
@app.get("/api/streets/{street_id}", response_model=Street)
|
|
@@ -476,6 +546,80 @@ async def delete_position_cascade(position_id: int):
|
|
| 476 |
raise HTTPException(status_code=500, detail=str(e))
|
| 477 |
|
| 478 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
@app.post("/api/search")
|
| 480 |
async def search_nearest(payload: dict = Body(...)):
|
| 481 |
"""Find the nearest DB position for each query point.
|
|
@@ -494,6 +638,71 @@ async def search_nearest(payload: dict = Body(...)):
|
|
| 494 |
raise HTTPException(status_code=500, detail=str(e))
|
| 495 |
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
# Serve admin static files (must be mounted last, after all API routes)
|
| 498 |
_admin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'admin')
|
| 499 |
if os.path.isdir(_admin_dir):
|
|
|
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
from starlette.middleware.base import BaseHTTPMiddleware
|
| 11 |
+
from starlette.responses import JSONResponse, Response
|
| 12 |
from typing import Optional, List
|
| 13 |
import logging
|
| 14 |
|
|
|
|
| 20 |
District, DistrictCreate, DistrictUpdate,
|
| 21 |
Street, StreetCreate, StreetUpdate,
|
| 22 |
Position, PositionCreate, PositionUpdate,
|
| 23 |
+
Group, GroupCreate, GroupUpdate,
|
| 24 |
)
|
| 25 |
from services.category_service import CategoryService
|
| 26 |
from services.intent_service import IntentService
|
|
|
|
| 91 |
raise HTTPException(status_code=500, detail="Database connection failed")
|
| 92 |
|
| 93 |
|
| 94 |
+
@app.get("/api/pois")
|
| 95 |
+
async def list_pois(city_id: Optional[int] = Query(None), district_id: Optional[int] = Query(None)):
|
| 96 |
+
try:
|
| 97 |
+
query = supabase.table("Pois").select("id, name, address, type, lat, long, city_id, district_id")
|
| 98 |
+
if district_id is not None:
|
| 99 |
+
query = query.eq("district_id", district_id)
|
| 100 |
+
elif city_id is not None:
|
| 101 |
+
query = query.eq("city_id", city_id)
|
| 102 |
+
result = query.order("name").execute()
|
| 103 |
+
return result.data or []
|
| 104 |
+
except Exception as e:
|
| 105 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@app.put("/api/pois/{poi_id}")
|
| 109 |
+
async def update_poi(poi_id: int, body: dict = Body(...)):
|
| 110 |
+
try:
|
| 111 |
+
return await MapsService.update_poi(poi_id, body)
|
| 112 |
+
except Exception as e:
|
| 113 |
+
logger.error(f"Error updating POI {poi_id}: {str(e)}")
|
| 114 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
@app.post("/api/pois/bulk")
|
| 118 |
+
async def update_pois_bulk(payload: List[dict] = Body(...)):
|
| 119 |
+
"""Bulk-update POIs.
|
| 120 |
+
Payload: [{poi_id, lat?, long?, name?, address?, type?}, ...]
|
| 121 |
+
"""
|
| 122 |
+
try:
|
| 123 |
+
updated = await MapsService.update_pois_bulk(payload)
|
| 124 |
+
return {"updated": updated}
|
| 125 |
+
except Exception as e:
|
| 126 |
+
logger.error(f"Error updating POIs bulk: {str(e)}")
|
| 127 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 128 |
+
|
| 129 |
+
|
| 130 |
# ==================== CATEGORIES ENDPOINTS ====================
|
| 131 |
|
| 132 |
@app.get("/api/categories", response_model=List[Category])
|
|
|
|
| 401 |
raise HTTPException(status_code=500, detail=str(e))
|
| 402 |
|
| 403 |
|
| 404 |
+
@app.post("/api/districts/search")
|
| 405 |
+
async def search_districts(payload: dict = Body(...)):
|
| 406 |
+
"""Search districts by text.
|
| 407 |
+
Payload: { text: str, city_id?: int, limit?: int }
|
| 408 |
+
Returns list of matches with score.
|
| 409 |
+
"""
|
| 410 |
+
try:
|
| 411 |
+
text = payload.get('text')
|
| 412 |
+
if not text:
|
| 413 |
+
raise HTTPException(status_code=422, detail='text is required')
|
| 414 |
+
city_id = payload.get('city_id')
|
| 415 |
+
limit = int(payload.get('limit') or 10)
|
| 416 |
+
results = await MapsService.search_districts_by_text(text, city_id=city_id, limit=limit)
|
| 417 |
+
return { 'results': results }
|
| 418 |
+
except HTTPException:
|
| 419 |
+
raise
|
| 420 |
+
except Exception as e:
|
| 421 |
+
logger.error(f"Error searching districts: {str(e)}")
|
| 422 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 423 |
+
|
| 424 |
+
|
| 425 |
@app.post("/api/streets", response_model=Street)
|
| 426 |
async def create_street(street: StreetCreate):
|
| 427 |
+
"""Create a new street. Returns 409 with {error:'duplicate', street:{id,name}} if similar exists."""
|
| 428 |
try:
|
| 429 |
return await MapsService.create_street(street)
|
| 430 |
except Exception as e:
|
| 431 |
+
msg = str(e)
|
| 432 |
+
# Structured duplicate signal: "Failed to create street: DUPLICATE_STREET:<id>:<name>"
|
| 433 |
+
inner = msg.replace("Failed to create street: ", "")
|
| 434 |
+
if inner.startswith("DUPLICATE_STREET:"):
|
| 435 |
+
parts = inner.split(":", 2) # ["DUPLICATE_STREET", "<id>", "<name>"]
|
| 436 |
+
dup_id = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else None
|
| 437 |
+
dup_name = parts[2] if len(parts) > 2 else ""
|
| 438 |
+
from fastapi.responses import JSONResponse
|
| 439 |
+
return JSONResponse(
|
| 440 |
+
status_code=409,
|
| 441 |
+
content={"error": "duplicate", "street": {"id": dup_id, "name": dup_name}}
|
| 442 |
+
)
|
| 443 |
+
logger.error(f"Error creating street: {msg}")
|
| 444 |
+
raise HTTPException(status_code=500, detail=msg)
|
| 445 |
|
| 446 |
|
| 447 |
@app.get("/api/streets/{street_id}", response_model=Street)
|
|
|
|
| 546 |
raise HTTPException(status_code=500, detail=str(e))
|
| 547 |
|
| 548 |
|
| 549 |
+
# ==================== GROUPS ENDPOINTS ====================
|
| 550 |
+
|
| 551 |
+
@app.get("/api/groups", response_model=List[Group])
|
| 552 |
+
async def list_groups():
|
| 553 |
+
"""Get all groups (used by the neighbor dropdown in Create Group)."""
|
| 554 |
+
try:
|
| 555 |
+
return await MapsService.get_groups()
|
| 556 |
+
except Exception as e:
|
| 557 |
+
logger.error(f"Error fetching groups: {str(e)}")
|
| 558 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
@app.get("/api/groups/{group_id}", response_model=Group)
|
| 562 |
+
async def get_group(group_id: int):
|
| 563 |
+
try:
|
| 564 |
+
group = await MapsService.get_group_by_id(group_id)
|
| 565 |
+
if not group:
|
| 566 |
+
raise HTTPException(status_code=404, detail="Group not found")
|
| 567 |
+
return group
|
| 568 |
+
except HTTPException:
|
| 569 |
+
raise
|
| 570 |
+
except Exception as e:
|
| 571 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
@app.post("/api/groups", response_model=Group)
|
| 575 |
+
async def create_group(group: GroupCreate):
|
| 576 |
+
"""Create a new group.
|
| 577 |
+
Body: { lat_center, long_center, streets: [street_id, ...], neighbor?: [group_id, ...] }
|
| 578 |
+
"""
|
| 579 |
+
try:
|
| 580 |
+
return await MapsService.create_group(group)
|
| 581 |
+
except Exception as e:
|
| 582 |
+
logger.error(f"Error creating group: {str(e)}")
|
| 583 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
@app.put("/api/groups/{group_id}", response_model=Group)
|
| 587 |
+
async def update_group(group_id: int, group: GroupUpdate):
|
| 588 |
+
try:
|
| 589 |
+
return await MapsService.update_group(group_id, group)
|
| 590 |
+
except Exception as e:
|
| 591 |
+
logger.error(f"Error updating group {group_id}: {str(e)}")
|
| 592 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
@app.delete("/api/groups/{group_id}")
|
| 596 |
+
async def delete_group(group_id: int):
|
| 597 |
+
try:
|
| 598 |
+
await MapsService.delete_group(group_id)
|
| 599 |
+
return {"message": "Group deleted"}
|
| 600 |
+
except Exception as e:
|
| 601 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
@app.post("/api/pathfind")
|
| 605 |
+
async def pathfind(payload: dict = Body(...)):
|
| 606 |
+
"""A* pathfinding between two position IDs.
|
| 607 |
+
Body: { start_id: int, end_id: int }
|
| 608 |
+
Returns: { found, node_path, street_path, street_details, total_m }
|
| 609 |
+
"""
|
| 610 |
+
try:
|
| 611 |
+
start_id = payload.get("start_id")
|
| 612 |
+
end_id = payload.get("end_id")
|
| 613 |
+
if start_id is None or end_id is None:
|
| 614 |
+
raise HTTPException(status_code=422, detail="start_id and end_id are required")
|
| 615 |
+
return await MapsService.pathfind(int(start_id), int(end_id))
|
| 616 |
+
except HTTPException:
|
| 617 |
+
raise
|
| 618 |
+
except Exception as e:
|
| 619 |
+
logger.error(f"Pathfind error: {str(e)}")
|
| 620 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 621 |
+
|
| 622 |
+
|
| 623 |
@app.post("/api/search")
|
| 624 |
async def search_nearest(payload: dict = Body(...)):
|
| 625 |
"""Find the nearest DB position for each query point.
|
|
|
|
| 638 |
raise HTTPException(status_code=500, detail=str(e))
|
| 639 |
|
| 640 |
|
| 641 |
+
@app.post("/api/search/point")
|
| 642 |
+
async def search_point(payload: dict = Body(...)):
|
| 643 |
+
"""Nearest single DB position to a lat/long (2-step Group strategy).
|
| 644 |
+
Body: { lat, lon } (also accepts long/lng for the longitude)
|
| 645 |
+
Returns: { found, position: {id,lat,lon,streets,street_names}|None, distance_m, group_id }
|
| 646 |
+
"""
|
| 647 |
+
try:
|
| 648 |
+
lat = payload.get("lat")
|
| 649 |
+
lon = payload.get("lon")
|
| 650 |
+
if lon is None:
|
| 651 |
+
lon = payload.get("long", payload.get("lng"))
|
| 652 |
+
if lat is None or lon is None:
|
| 653 |
+
raise HTTPException(status_code=422, detail="lat and lon are required")
|
| 654 |
+
return await MapsService.nearest_position(float(lat), float(lon))
|
| 655 |
+
except HTTPException:
|
| 656 |
+
raise
|
| 657 |
+
except Exception as e:
|
| 658 |
+
logger.error(f"Error searching nearest point: {str(e)}")
|
| 659 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 660 |
+
|
| 661 |
+
|
| 662 |
+
# ==================== TEST / UTILITY ENDPOINTS ====================
|
| 663 |
+
|
| 664 |
+
@app.get("/search/uuid")
|
| 665 |
+
async def search_uuid(id: str = Query(..., description="ID string to search for in utest table")):
|
| 666 |
+
"""Search the utest table for the most similar id string.
|
| 667 |
+
Returns the best-matching row and its normalised Levenshtein score (0.0 = exact).
|
| 668 |
+
"""
|
| 669 |
+
try:
|
| 670 |
+
if not id or not id.strip():
|
| 671 |
+
raise HTTPException(status_code=422, detail="id query param is required")
|
| 672 |
+
return await MapsService.search_uuid(id.strip())
|
| 673 |
+
except HTTPException:
|
| 674 |
+
raise
|
| 675 |
+
except Exception as e:
|
| 676 |
+
logger.error(f"search_uuid error: {str(e)}")
|
| 677 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
# ==================== EXPORT ENDPOINTS ====================
|
| 681 |
+
|
| 682 |
+
@app.get("/export/position.bin")
|
| 683 |
+
async def export_position_bin():
|
| 684 |
+
"""Export the full position graph as a compact binary file for A* pathfinding.
|
| 685 |
+
|
| 686 |
+
Format: POSBIN v1 — header (16B) + node ids (4B each) + edges (12B each).
|
| 687 |
+
Nodes contain id only; client maps back to DB to retrieve lat/lon/name.
|
| 688 |
+
Edges are undirected (from_id < to_id); client mirrors both directions for A*.
|
| 689 |
+
"""
|
| 690 |
+
try:
|
| 691 |
+
data = await MapsService.export_graph_bin()
|
| 692 |
+
logger.info(f"Exported position.bin: {len(data)} bytes")
|
| 693 |
+
return Response(
|
| 694 |
+
content=data,
|
| 695 |
+
media_type="application/octet-stream",
|
| 696 |
+
headers={
|
| 697 |
+
"Content-Disposition": 'attachment; filename="position.bin"',
|
| 698 |
+
"Content-Length": str(len(data)),
|
| 699 |
+
},
|
| 700 |
+
)
|
| 701 |
+
except Exception as e:
|
| 702 |
+
logger.error(f"Error exporting position.bin: {str(e)}")
|
| 703 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 704 |
+
|
| 705 |
+
|
| 706 |
# Serve admin static files (must be mounted last, after all API routes)
|
| 707 |
_admin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'admin')
|
| 708 |
if os.path.isdir(_admin_dir):
|
backend/models.py
CHANGED
|
@@ -165,3 +165,31 @@ class PositionUpdate(BaseModel):
|
|
| 165 |
lane: Optional[int] = None
|
| 166 |
tool: Optional[int] = None
|
| 167 |
flooding: Optional[bool] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
lane: Optional[int] = None
|
| 166 |
tool: Optional[int] = None
|
| 167 |
flooding: Optional[bool] = None
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# ==================== GROUPS MODEL ====================
|
| 171 |
+
|
| 172 |
+
class Group(BaseModel):
|
| 173 |
+
id: Optional[int] = None
|
| 174 |
+
lat_center: Optional[float] = None
|
| 175 |
+
long_center: Optional[float] = None
|
| 176 |
+
streets: Optional[list] = None # JSON array of street ids
|
| 177 |
+
neighbor: Optional[list] = None # JSON array of group ids
|
| 178 |
+
created_at: Optional[str] = None
|
| 179 |
+
|
| 180 |
+
class Config:
|
| 181 |
+
from_attributes = True
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
class GroupCreate(BaseModel):
|
| 185 |
+
lat_center: float
|
| 186 |
+
long_center: float
|
| 187 |
+
streets: List[int]
|
| 188 |
+
neighbor: Optional[List[int]] = None
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
class GroupUpdate(BaseModel):
|
| 192 |
+
lat_center: Optional[float] = None
|
| 193 |
+
long_center: Optional[float] = None
|
| 194 |
+
streets: Optional[List[int]] = None
|
| 195 |
+
neighbor: Optional[List[int]] = None
|
backend/php_unserialize.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PHP unserialize — parse PHP serialized strings into Python objects.
|
| 3 |
+
Usage:
|
| 4 |
+
python php_unserialize.py 'a:2:{s:3:"foo";i:1;s:3:"bar";b:1;}'
|
| 5 |
+
or import and call php_unserialize(data)
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import sys
|
| 9 |
+
import json
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def php_unserialize(data: str):
|
| 13 |
+
"""Deserialize a PHP serialized string into a Python object."""
|
| 14 |
+
data = data.strip()
|
| 15 |
+
obj, pos = _parse(data, 0)
|
| 16 |
+
return obj
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# ── Internal parser ──────────────────────────────────────────────────────────
|
| 20 |
+
|
| 21 |
+
def _parse(s: str, pos: int):
|
| 22 |
+
if pos >= len(s):
|
| 23 |
+
raise ValueError(f"Unexpected end of data at position {pos}")
|
| 24 |
+
|
| 25 |
+
token = s[pos]
|
| 26 |
+
|
| 27 |
+
if token == 'N': # null → N;
|
| 28 |
+
return None, pos + 2 # skip 'N' and ';'
|
| 29 |
+
|
| 30 |
+
if token == 'b': # bool → b:0; or b:1;
|
| 31 |
+
val = s[pos + 2] == '1'
|
| 32 |
+
return val, pos + 4
|
| 33 |
+
|
| 34 |
+
if token == 'i': # int → i:123;
|
| 35 |
+
end = s.index(';', pos)
|
| 36 |
+
return int(s[pos + 2:end]), end + 1
|
| 37 |
+
|
| 38 |
+
if token == 'd': # float → d:1.5;
|
| 39 |
+
end = s.index(';', pos)
|
| 40 |
+
return float(s[pos + 2:end]), end + 1
|
| 41 |
+
|
| 42 |
+
if token == 's': # string → s:3:"foo";
|
| 43 |
+
colon = s.index(':', pos + 2)
|
| 44 |
+
length = int(s[pos + 2:colon])
|
| 45 |
+
# skip colon and opening quote
|
| 46 |
+
start = colon + 2
|
| 47 |
+
value = s[start:start + length]
|
| 48 |
+
return value, start + length + 2 # skip closing '";'
|
| 49 |
+
|
| 50 |
+
if token == 'a': # array → a:2:{...}
|
| 51 |
+
colon = s.index(':', pos + 2)
|
| 52 |
+
count = int(s[pos + 2:colon])
|
| 53 |
+
pos = colon + 2 # skip past '{'
|
| 54 |
+
result = {}
|
| 55 |
+
for _ in range(count):
|
| 56 |
+
key, pos = _parse(s, pos)
|
| 57 |
+
val, pos = _parse(s, pos)
|
| 58 |
+
result[key] = val
|
| 59 |
+
return result, pos + 1 # skip '}'
|
| 60 |
+
|
| 61 |
+
if token == 'O': # object → O:9:"ClassName":2:{...}
|
| 62 |
+
# class name length
|
| 63 |
+
colon1 = s.index(':', pos + 2)
|
| 64 |
+
cls_len = int(s[pos + 2:colon1])
|
| 65 |
+
cls_start = colon1 + 2
|
| 66 |
+
class_name = s[cls_start:cls_start + cls_len]
|
| 67 |
+
# property count
|
| 68 |
+
colon2 = s.index(':', cls_start + cls_len + 2)
|
| 69 |
+
prop_count = int(s[cls_start + cls_len + 2:colon2])
|
| 70 |
+
pos = colon2 + 2 # skip past '{'
|
| 71 |
+
props = {}
|
| 72 |
+
for _ in range(prop_count):
|
| 73 |
+
key, pos = _parse(s, pos)
|
| 74 |
+
val, pos = _parse(s, pos)
|
| 75 |
+
props[key] = val
|
| 76 |
+
result = {"__class__": class_name, **props}
|
| 77 |
+
return result, pos + 1 # skip '}'
|
| 78 |
+
|
| 79 |
+
if token == 'C': # custom serialized → C:classname:len:{data}
|
| 80 |
+
colon1 = s.index(':', pos + 2)
|
| 81 |
+
cls_len = int(s[pos + 2:colon1])
|
| 82 |
+
cls_start = colon1 + 2
|
| 83 |
+
class_name = s[cls_start:cls_start + cls_len]
|
| 84 |
+
colon2 = s.index(':', cls_start + cls_len + 1)
|
| 85 |
+
data_len = int(s[cls_start + cls_len + 1:colon2])
|
| 86 |
+
data_start = colon2 + 2
|
| 87 |
+
raw = s[data_start:data_start + data_len]
|
| 88 |
+
result = {"__class__": class_name, "__data__": raw}
|
| 89 |
+
return result, data_start + data_len + 1
|
| 90 |
+
|
| 91 |
+
raise ValueError(f"Unknown type token '{token}' at position {pos}")
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# ── Serializer ───────────────────────────────────────────────────────────────
|
| 95 |
+
|
| 96 |
+
def php_serialize(obj) -> str:
|
| 97 |
+
"""Serialize a Python object back into a PHP serialized string."""
|
| 98 |
+
if obj is None:
|
| 99 |
+
return 'N;'
|
| 100 |
+
if isinstance(obj, bool):
|
| 101 |
+
return f'b:{1 if obj else 0};'
|
| 102 |
+
if isinstance(obj, int):
|
| 103 |
+
return f'i:{obj};'
|
| 104 |
+
if isinstance(obj, float):
|
| 105 |
+
return f'd:{obj};'
|
| 106 |
+
if isinstance(obj, str):
|
| 107 |
+
return f's:{len(obj.encode("utf-8"))}:"{obj}";'
|
| 108 |
+
if isinstance(obj, (list, tuple)):
|
| 109 |
+
parts = ''.join(php_serialize(i) + php_serialize(v) for i, v in enumerate(obj))
|
| 110 |
+
return f'a:{len(obj)}:{{{parts}}}'
|
| 111 |
+
if isinstance(obj, dict):
|
| 112 |
+
# Object with __class__ key → O type
|
| 113 |
+
if '__class__' in obj:
|
| 114 |
+
class_name = obj['__class__']
|
| 115 |
+
props = {k: v for k, v in obj.items() if k != '__class__'}
|
| 116 |
+
parts = ''.join(php_serialize(k) + php_serialize(v) for k, v in props.items())
|
| 117 |
+
return f'O:{len(class_name)}:"{class_name}":{len(props)}:{{{parts}}}'
|
| 118 |
+
parts = ''.join(php_serialize(k) + php_serialize(v) for k, v in obj.items())
|
| 119 |
+
return f'a:{len(obj)}:{{{parts}}}'
|
| 120 |
+
raise TypeError(f"Cannot serialize type: {type(obj)}")
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# ── Input string ─────────────────────────────────────────────────────────────
|
| 124 |
+
|
| 125 |
+
PHP_STRING = 'a:4:{s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"R4IF-ClDHXxblLcG0L7MgsLvfBIMAvi_skumNFQwkDc";}s:6:"module";a:276:{s:7:"address";i:0;s:13:"admin_toolbar";i:0;s:19:"admin_toolbar_tools";i:0;s:23:"advanced_text_formatter";i:0;s:10:"autologout";i:0;s:3:"ban";i:0;s:8:"big_pipe";i:0;s:5:"blazy";i:0;s:5:"block";i:0;s:13:"block_content";i:0;s:11:"block_field";i:0;s:17:"bootstrap_library";i:0;s:10:"breakpoint";i:0;s:13:"campaign_erun";i:0;s:16:"campaign_lead_ad";i:0;s:22:"campaign_sportify_2021";i:0;s:7:"captcha";i:0;s:9:"ckeditor5";i:0;s:17:"classy_paragraphs";i:0;s:8:"colorbox";i:0;s:7:"comment";i:0;s:6:"config";i:0;s:13:"config_filter";i:0;s:13:"config_ignore";i:0;s:12:"config_pages";i:0;s:12:"config_split";i:0;s:13:"config_update";i:0;s:7:"contact";i:0;s:7:"context";i:0;s:18:"context_breadcrumb";i:0;s:10:"context_ui";i:0;s:10:"contextual";i:0;s:12:"core_context";i:0;s:4:"crop";i:0;s:17:"csv_serialization";i:0;s:6:"ctools";i:0;s:12:"ctools_block";i:0;s:9:"datalayer";i:0;s:8:"datetime";i:0;s:10:"dropzonejs";i:0;s:20:"dropzonejs_eb_widget";i:0;s:15:"dsu_c_accordion";i:0;s:26:"dsu_c_background_component";i:0;s:18:"dsu_c_contentcycle";i:0;s:17:"dsu_c_entitycycle";i:0;s:22:"dsu_c_extend_component";i:0;s:19:"dsu_c_externalvideo";i:0;s:13:"dsu_c_gallery";i:0;s:11:"dsu_c_image";i:0;s:29:"dsu_c_link_document_container";i:0;s:21:"dsu_c_product_extends";i:0;s:18:"dsu_c_sharebuttons";i:0;s:19:"dsu_c_sideimagetext";i:0;s:12:"dsu_c_slider";i:0;s:10:"dsu_c_text";i:0;s:15:"dsu_c_text_card";i:0;s:14:"dsu_c_text_dyk";i:0;s:10:"dsu_c_view";i:0;s:8:"dsu_core";i:0;s:19:"dsu_ratings_reviews";i:0;s:6:"editor";i:0;s:5:"embed";i:0;s:7:"encrypt";i:0;s:14:"entity_browser";i:0;s:22:"entity_class_formatter";i:0;s:12:"entity_clone";i:0;s:12:"entity_embed";i:0;s:26:"entity_reference_revisions";i:0;s:17:"external_hreflang";i:0;s:12:"externalauth";i:0;s:6:"facets";i:0;s:8:"features";i:0;s:11:"features_ui";i:0;s:5:"field";i:0;s:11:"field_group";i:0;s:11:"field_timer";i:0;s:8:"field_ui";i:0;s:4:"file";i:0;s:12:"file_browser";i:0;s:28:"file_upload_secure_validator";i:0;s:6:"filter";i:0;s:8:"fivestar";i:0;s:4:"flag";i:0;s:11:"focal_point";i:0;s:11:"geolocation";i:0;s:5:"gigya";i:0;s:10:"gigya_raas";i:0;s:19:"gigya_user_deletion";i:0;s:16:"google_analytics";i:0;s:15:"google_optimize";i:0;s:18:"google_optimize_js";i:0;s:10:"google_tag";i:0;s:8:"honeypot";i:0;s:5:"image";i:0;s:17:"image_widget_crop";i:0;s:18:"inline_entity_form";i:0;s:9:"jquery_ui";i:0;s:22:"jquery_ui_autocomplete";i:0;s:16:"jquery_ui_button";i:0;s:23:"jquery_ui_checkboxradio";i:0;s:22:"jquery_ui_controlgroup";i:0;s:20:"jquery_ui_datepicker";i:0;s:19:"jquery_ui_draggable";i:0;s:19:"jquery_ui_droppable";i:0;s:14:"jquery_ui_menu";i:0;s:17:"jquery_ui_spinner";i:0;s:3:"key";i:0;s:8:"language";i:0;s:14:"layout_builder";i:0;s:38:"layout_builder_expose_all_field_blocks";i:0;s:16:"layout_discovery";i:0;s:14:"layout_library";i:0;s:4:"link";i:0;s:15:"link_attributes";i:0;s:33:"link_attributes_menu_link_content";i:0;s:8:"ln_adimo";i:0;s:10:"ln_article";i:0;s:20:"ln_c_background_only";i:0;s:22:"ln_c_buy_now_component";i:0;s:9:"ln_c_card";i:0;s:13:"ln_c_cardgrid";i:0;s:11:"ln_c_spacer";i:0;s:11:"ln_fusepump";i:0;s:11:"ln_hreflang";i:0;s:9:"ln_m_core";i:0;s:13:"ln_m_document";i:0;s:19:"ln_m_external_video";i:0;s:9:"ln_n_core";i:0;s:15:"ln_price_spider";i:0;s:10:"ln_product";i:0;s:11:"ln_sso_saml";i:0;s:17:"ln_tint_connector";i:0;s:6:"locale";i:0;s:14:"login_security";i:0;s:5:"media";i:0;s:13:"media_library";i:0;s:8:"memcache";i:0;s:17:"menu_link_content";i:0;s:7:"menu_ui";i:0;s:7:"metatag";i:0;s:16:"metatag_hreflang";i:0;s:18:"metatag_open_graph";i:0;s:27:"metatag_open_graph_products";i:0;s:21:"metatag_twitter_cards";i:0;s:7:"migrate";i:0;s:30:"migrate_example_advanced_setup";i:0;s:21:"migrate_example_setup";i:0;s:12:"migrate_plus";i:0;s:18:"migrate_source_csv";i:0;s:13:"migrate_tools";i:0;s:10:"milo_covid";i:0;s:16:"milo_gtm_tagging";i:0;s:14:"milo_teen_quiz";i:0;s:18:"milo_vietnam_pages";i:0;s:5:"mysql";i:0;s:4:"node";i:0;s:10:"noreferrer";i:0;s:16:"ogilvy_c_article";i:0;s:18:"ogilvy_c_milestone";i:0;s:26:"ogilvy_c_sharebuttons_zalo";i:0;s:13:"ogilvy_c_text";i:0;s:9:"ogilvy_co";i:0;s:30:"ogilvy_co_article_listing_3in1";i:0;s:26:"ogilvy_co_bar_notification";i:0;s:29:"ogilvy_co_carousel_fulllength";i:0;s:28:"ogilvy_co_carousel_imageetch";i:0;s:27:"ogilvy_co_ciam_gigya_screen";i:0;s:25:"ogilvy_co_content_related";i:0;s:20:"ogilvy_co_error_page";i:0;s:23:"ogilvy_co_faq_accordion";i:0;s:24:"ogilvy_co_faq_search_bar";i:0;s:38:"ogilvy_co_infographic_nutritionalvalue";i:0;s:26:"ogilvy_co_intro_fulllength";i:0;s:24:"ogilvy_co_masthead_short";i:0;s:30:"ogilvy_co_nutritionalindicator";i:0;s:34:"ogilvy_co_nutritionalindicator_tab";i:0;s:25:"ogilvy_co_partnerslisting";i:0;s:32:"ogilvy_co_recipedescription_list";i:0;s:34:"ogilvy_co_recipedetails_headerdesc";i:0;s:38:"ogilvy_co_recipedetails_productmention";i:0;s:34:"ogilvy_co_recipedetails_stepbystep";i:0;s:32:"ogilvy_co_reviews_ratings_teaser";i:0;s:29:"ogilvy_co_section_teaser_card";i:0;s:22:"ogilvy_co_sportscentre";i:0;s:32:"ogilvy_co_sportslanding_masthead";i:0;s:14:"ogilvy_co_tint";i:0;s:35:"ogilvy_co_videolisting_1hero_3small";i:0;s:23:"ogilvy_co_view_selector";i:0;s:20:"ogilvy_co_winnerlist";i:0;s:13:"ogilvy_common";i:0;s:12:"ogilvy_gigya";i:0;s:20:"ogilvy_module_extend";i:0;s:29:"ogilvy_recipe_structured_data";i:0;s:13:"ogilvy_search";i:0;s:19:"ogilvy_sport_center";i:0;s:26:"ogilvy_template_suggestion";i:0;s:7:"options";i:0;s:13:"options_table";i:0;s:6:"panels";i:0;s:10:"panels_ipe";i:0;s:18:"paragraphs_browser";i:0;s:19:"paragraphs_features";i:0;s:20:"paragraphs_previewer";i:0;s:31:"password_policy_character_types";i:0;s:26:"password_policy_characters";i:0;s:23:"password_policy_history";i:0;s:22:"password_policy_length";i:0;s:24:"password_policy_username";i:0;s:4:"path";i:0;s:10:"path_alias";i:0;s:6:"phpass";i:0;s:15:"project_browser";i:0;s:8:"real_aes";i:0;s:9:"recaptcha";i:0;s:12:"recaptcha_v3";i:0;s:8:"redirect";i:0;s:16:"responsive_image";i:0;s:4:"rest";i:0;s:20:"restrict_route_by_ip";i:0;s:6:"restui";i:0;s:9:"robotstxt";i:0;s:8:"samlauth";i:0;s:19:"samlauth_user_roles";i:0;s:9:"scheduler";i:0;s:14:"schema_article";i:0;s:14:"schema_metatag";i:0;s:19:"schema_organization";i:0;s:14:"schema_product";i:0;s:13:"schema_recipe";i:0;s:15:"schema_web_site";i:0;s:10:"search_api";i:0;s:13:"search_api_db";i:0;s:6:"seckit";i:0;s:13:"serialization";i:0;s:13:"session_limit";i:0;s:6:"shield";i:0;s:8:"shortcut";i:0;s:5:"slick";i:0;s:21:"slick_entityreference";i:0;s:16:"slick_paragraphs";i:0;s:8:"slick_ui";i:0;s:12:"social_media";i:0;s:15:"svg_image_field";i:0;s:6:"syslog";i:0;s:6:"system";i:0;s:8:"taxonomy";i:0;s:4:"text";i:0;s:5:"token";i:0;s:7:"toolbar";i:0;s:10:"twig_tweak";i:0;s:4:"user";i:0;s:17:"video_embed_field";i:0;s:21:"views_infinite_scroll";i:0;s:8:"views_ui";i:0;s:14:"viewsreference";i:0;s:25:"vn_campaign_canister_2021";i:0;s:9:"votingapi";i:0;s:7:"webform";i:0;s:13:"webform_gigya";i:0;s:12:"webform_rest";i:0;s:23:"webform_scheduled_email";i:0;s:10:"webform_ui";i:0;s:13:"webform_views";i:0;s:17:"xls_serialization";i:0;s:2:"ds";i:1;s:10:"dsu_c_core";i:1;s:8:"pathauto";i:1;s:10:"xmlsitemap";i:1;s:15:"password_policy";i:10;s:5:"views";i:10;s:10:"paragraphs";i:11;s:15:"paragraphs_sets";i:12;s:13:"paragraphs_ee";i:15;s:12:"dsu_security";i:50;s:9:"lightnest";i:1000;s:3:"shs";i:1000;}s:5:"theme";a:5:{s:16:"bootstrap_barrio";i:0;s:5:"seven";i:0;s:6:"stable";i:0;s:4:"milo";i:0;s:5:"claro";i:0;}s:7:"profile";s:9:"lightnest";}'
|
| 126 |
+
|
| 127 |
+
# ── CLI ──────────────────────────────────────────────────────────────────────
|
| 128 |
+
|
| 129 |
+
if __name__ == "__main__":
|
| 130 |
+
raw = sys.argv[1] if len(sys.argv) > 1 else PHP_STRING
|
| 131 |
+
try:
|
| 132 |
+
result = php_unserialize(raw)
|
| 133 |
+
|
| 134 |
+
# Remove milo_teen_quiz from module dict
|
| 135 |
+
if isinstance(result.get('module'), dict) and 'milo_teen_quiz' in result['module']:
|
| 136 |
+
del result['module']['milo_teen_quiz']
|
| 137 |
+
print("[removed] milo_teen_quiz from module", file=sys.stderr)
|
| 138 |
+
|
| 139 |
+
serialized = php_serialize(result)
|
| 140 |
+
print(serialized)
|
| 141 |
+
except Exception as e:
|
| 142 |
+
print(f"Error: {e}", file=sys.stderr)
|
| 143 |
+
sys.exit(1)
|
backend/position.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5cf7c3998a47e495367f5dae77c09b8040c30f99c006b78bee36232317eed970
|
| 3 |
+
size 27852
|
backend/services/maps_service.py
CHANGED
|
@@ -4,6 +4,7 @@ from models import (
|
|
| 4 |
District, DistrictCreate, DistrictUpdate,
|
| 5 |
Street, StreetCreate, StreetUpdate,
|
| 6 |
Position, PositionCreate, PositionUpdate,
|
|
|
|
| 7 |
)
|
| 8 |
from typing import List, Optional
|
| 9 |
import unicodedata
|
|
@@ -175,51 +176,24 @@ class MapsService:
|
|
| 175 |
return prev[-1]
|
| 176 |
|
| 177 |
name_norm = _normalize(data.name)
|
| 178 |
-
city_id = data.city_id
|
| 179 |
-
district_id = data.district_id
|
| 180 |
|
| 181 |
-
#
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
query = query.eq("district_id", district_id)
|
| 185 |
-
elif city_id is not None:
|
| 186 |
-
query = query.eq("city_id", city_id)
|
| 187 |
-
existing_res = query.execute()
|
| 188 |
existing_list = existing_res.data or []
|
| 189 |
for item in existing_list:
|
| 190 |
existing_name = item.get('name') or ''
|
| 191 |
en_norm = _normalize(existing_name)
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
| 196 |
max_dist = max(2, int(max(len(en_norm), len(name_norm)) * 0.2))
|
| 197 |
dist = _levenshtein(en_norm, name_norm)
|
| 198 |
-
# debug log
|
| 199 |
-
try:
|
| 200 |
-
import logging
|
| 201 |
-
logging.getLogger(__name__).info(f"create_street: compare '{name_norm}' vs '{en_norm}' -> dist={dist} threshold={max_dist}")
|
| 202 |
-
except Exception:
|
| 203 |
-
pass
|
| 204 |
if dist <= max_dist:
|
| 205 |
-
raise Exception(f"
|
| 206 |
-
|
| 207 |
-
# Also perform a global fuzzy search to catch similar names in other districts/cities
|
| 208 |
-
try:
|
| 209 |
-
global_results = await MapsService.search_streets_by_text(data.name, district_id=None, city_id=None, limit=10)
|
| 210 |
-
# Accept if any match has a low score (<= 0.15)
|
| 211 |
-
for r in (global_results or []):
|
| 212 |
-
if float(r.get('score', 1.0)) <= 0.15:
|
| 213 |
-
raise Exception(f"Similar street exists elsewhere: {r.get('name')} (id={r.get('id')})")
|
| 214 |
-
except Exception as e:
|
| 215 |
-
# If search_streets_by_text raised an exception because of DB, re-raise as generic
|
| 216 |
-
if str(e).startswith('Failed to create street'):
|
| 217 |
-
raise
|
| 218 |
-
# If we raised above due to similarity, pass it up
|
| 219 |
-
if 'Similar street exists' in str(e) or 'Similar street exists elsewhere' in str(e):
|
| 220 |
-
raise
|
| 221 |
-
# otherwise ignore search errors and proceed with insert
|
| 222 |
-
pass
|
| 223 |
|
| 224 |
# No similar found -> proceed to insert
|
| 225 |
next_id = _next_id("Streets")
|
|
@@ -331,6 +305,83 @@ class MapsService:
|
|
| 331 |
except Exception as e:
|
| 332 |
raise Exception(f"Failed to search streets: {str(e)}")
|
| 333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
# ==================== POSITIONS ====================
|
| 335 |
|
| 336 |
@staticmethod
|
|
@@ -390,9 +441,12 @@ class MapsService:
|
|
| 390 |
@staticmethod
|
| 391 |
async def update_position(position_id: int, data: PositionUpdate) -> Position:
|
| 392 |
try:
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
| 394 |
result = supabase.table("Positions").update(payload).eq("id", position_id).execute()
|
| 395 |
-
return Position(**result.data[0])
|
| 396 |
except Exception as e:
|
| 397 |
raise Exception(f"Failed to update position: {str(e)}")
|
| 398 |
|
|
@@ -478,6 +532,405 @@ class MapsService:
|
|
| 478 |
except Exception as e:
|
| 479 |
raise Exception(f"Failed to create/update positions bulk: {str(e)}")
|
| 480 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
@staticmethod
|
| 482 |
async def search_nearest(points: list) -> dict:
|
| 483 |
"""
|
|
@@ -585,6 +1038,23 @@ class MapsService:
|
|
| 585 |
return ". ".join(instructions) + "."
|
| 586 |
|
| 587 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
result = supabase.table("Positions").select("*").execute()
|
| 589 |
all_positions = result.data or []
|
| 590 |
if not all_positions:
|
|
@@ -620,20 +1090,7 @@ class MapsService:
|
|
| 620 |
return f"~{m}m" if m < 1000 else f"~{round(m / 1000, 1)}km"
|
| 621 |
|
| 622 |
# ── Single-point mode: return nearest ────────────────────────────
|
| 623 |
-
|
| 624 |
-
qlat, qlon = float(points[0]["lat"]), float(points[0]["lon"])
|
| 625 |
-
nid, dist_m = find_nearest_id(qlat, qlon)
|
| 626 |
-
if nid is None:
|
| 627 |
-
return {"data": [], "label": "Không tìm thấy", "tts": "", "warning": "", "message": ""}
|
| 628 |
-
pt = make_result_point(pos_by_id[nid])
|
| 629 |
-
sname = pt["street_names"][0] if pt["street_names"] else "vị trí không xác định"
|
| 630 |
-
return {
|
| 631 |
-
"data": [pt],
|
| 632 |
-
"label": f"Điểm gần nhất: {sname} ({dist_label(round(dist_m))})",
|
| 633 |
-
"tts": f"Vị trí gần nhất nằm trên {sname}, cách bạn khoảng {round(dist_m)} mét.",
|
| 634 |
-
"warning": "",
|
| 635 |
-
"message": f"Khoảng cách: ~{round(dist_m)}m",
|
| 636 |
-
}
|
| 637 |
|
| 638 |
# ── Two-point mode: shortest path via Dijkstra ───────────────────
|
| 639 |
q1, q2 = points[0], points[1]
|
|
@@ -729,6 +1186,60 @@ class MapsService:
|
|
| 729 |
except Exception as e:
|
| 730 |
raise Exception(f"Search nearest failed: {str(e)}")
|
| 731 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
@staticmethod
|
| 733 |
async def delete_position_cascade(position_id: int) -> bool:
|
| 734 |
"""Delete a position and remove its id from any Streets.positions arrays listed in position.streets"""
|
|
|
|
| 4 |
District, DistrictCreate, DistrictUpdate,
|
| 5 |
Street, StreetCreate, StreetUpdate,
|
| 6 |
Position, PositionCreate, PositionUpdate,
|
| 7 |
+
Group, GroupCreate, GroupUpdate,
|
| 8 |
)
|
| 9 |
from typing import List, Optional
|
| 10 |
import unicodedata
|
|
|
|
| 176 |
return prev[-1]
|
| 177 |
|
| 178 |
name_norm = _normalize(data.name)
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
# Only check duplicates WITHIN THE SAME district -> cho phép cùng tên đường ở phường khác.
|
| 181 |
+
# Chỉ chặn khi trùng tên VÀ trùng district_id.
|
| 182 |
+
existing_res = supabase.table("Streets").select("id, name, district_id, city_id").eq("district_id", data.district_id).order("name").execute()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
existing_list = existing_res.data or []
|
| 184 |
for item in existing_list:
|
| 185 |
existing_name = item.get('name') or ''
|
| 186 |
en_norm = _normalize(existing_name)
|
| 187 |
+
if not en_norm:
|
| 188 |
+
continue
|
| 189 |
+
# exact or containment match
|
| 190 |
+
if en_norm == name_norm or (name_norm and (name_norm in en_norm or en_norm in name_norm)):
|
| 191 |
+
raise Exception(f"DUPLICATE_STREET:{item.get('id')}:{existing_name}")
|
| 192 |
+
# edit distance threshold scales with length
|
| 193 |
max_dist = max(2, int(max(len(en_norm), len(name_norm)) * 0.2))
|
| 194 |
dist = _levenshtein(en_norm, name_norm)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
if dist <= max_dist:
|
| 196 |
+
raise Exception(f"DUPLICATE_STREET:{item.get('id')}:{existing_name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
# No similar found -> proceed to insert
|
| 199 |
next_id = _next_id("Streets")
|
|
|
|
| 305 |
except Exception as e:
|
| 306 |
raise Exception(f"Failed to search streets: {str(e)}")
|
| 307 |
|
| 308 |
+
@staticmethod
|
| 309 |
+
async def search_districts_by_text(text: str, city_id: Optional[int] = None, limit: int = 10) -> list:
|
| 310 |
+
"""Search districts by text with normalization and fuzzy matching.
|
| 311 |
+
Matches against both `name` and `lname` (best score wins).
|
| 312 |
+
Returns list of dicts: {id, name, lname, city, score}
|
| 313 |
+
Lower score == better match.
|
| 314 |
+
"""
|
| 315 |
+
try:
|
| 316 |
+
if not text or not str(text).strip():
|
| 317 |
+
return []
|
| 318 |
+
import re
|
| 319 |
+
def _normalize(s: str) -> str:
|
| 320 |
+
s = str(s or '').strip().lower()
|
| 321 |
+
s = unicodedata.normalize('NFKD', s)
|
| 322 |
+
s = ''.join(ch for ch in s if not unicodedata.combining(ch))
|
| 323 |
+
s = re.sub(r'[^0-9a-z\s]', ' ', s)
|
| 324 |
+
s = ' '.join(s.split())
|
| 325 |
+
return s
|
| 326 |
+
|
| 327 |
+
def _lev(a: str, b: str) -> int:
|
| 328 |
+
if a == b:
|
| 329 |
+
return 0
|
| 330 |
+
if len(a) == 0:
|
| 331 |
+
return len(b)
|
| 332 |
+
if len(b) == 0:
|
| 333 |
+
return len(a)
|
| 334 |
+
prev = list(range(len(b) + 1))
|
| 335 |
+
for i, ca in enumerate(a, start=1):
|
| 336 |
+
curr = [i]
|
| 337 |
+
for j, cb in enumerate(b, start=1):
|
| 338 |
+
insert_cost = curr[j-1] + 1
|
| 339 |
+
delete_cost = prev[j] + 1
|
| 340 |
+
replace_cost = prev[j-1] + (0 if ca == cb else 1)
|
| 341 |
+
curr.append(min(insert_cost, delete_cost, replace_cost))
|
| 342 |
+
prev = curr
|
| 343 |
+
return prev[-1]
|
| 344 |
+
|
| 345 |
+
def _score(query_norm: str, cand_norm: str) -> float:
|
| 346 |
+
if not cand_norm:
|
| 347 |
+
return 1.0
|
| 348 |
+
if cand_norm == query_norm:
|
| 349 |
+
return 0.0
|
| 350 |
+
if query_norm in cand_norm or cand_norm in query_norm:
|
| 351 |
+
return 0.1
|
| 352 |
+
dist = _lev(query_norm, cand_norm)
|
| 353 |
+
max_len = max(len(query_norm), len(cand_norm), 1)
|
| 354 |
+
return dist / max_len
|
| 355 |
+
|
| 356 |
+
q = _normalize(text)
|
| 357 |
+
# Fetch candidate districts, optionally filtered by city
|
| 358 |
+
query = supabase.table("Districts").select("id, name, lname, city").order("name")
|
| 359 |
+
if city_id is not None:
|
| 360 |
+
query = query.eq("city", city_id)
|
| 361 |
+
res = query.execute()
|
| 362 |
+
candidates = res.data or []
|
| 363 |
+
|
| 364 |
+
scored = []
|
| 365 |
+
for item in candidates:
|
| 366 |
+
name = item.get('name') or ''
|
| 367 |
+
lname = item.get('lname') or ''
|
| 368 |
+
score = _score(q, _normalize(name))
|
| 369 |
+
if lname:
|
| 370 |
+
score = min(score, _score(q, _normalize(lname)))
|
| 371 |
+
scored.append({
|
| 372 |
+
'id': item.get('id'),
|
| 373 |
+
'name': name,
|
| 374 |
+
'lname': item.get('lname'),
|
| 375 |
+
'city': item.get('city'),
|
| 376 |
+
'score': round(float(score), 3)
|
| 377 |
+
})
|
| 378 |
+
|
| 379 |
+
# sort by score asc, then by name
|
| 380 |
+
scored.sort(key=lambda x: (x['score'], x['name']))
|
| 381 |
+
return scored[:limit]
|
| 382 |
+
except Exception as e:
|
| 383 |
+
raise Exception(f"Failed to search districts: {str(e)}")
|
| 384 |
+
|
| 385 |
# ==================== POSITIONS ====================
|
| 386 |
|
| 387 |
@staticmethod
|
|
|
|
| 441 |
@staticmethod
|
| 442 |
async def update_position(position_id: int, data: PositionUpdate) -> Position:
|
| 443 |
try:
|
| 444 |
+
raw = {k: v for k, v in data.model_dump().items() if v is not None}
|
| 445 |
+
# Map model fields -> DB columns (x->long, y->lat); 'street_id' is not a column
|
| 446 |
+
col_map = {'x': 'long', 'y': 'lat'}
|
| 447 |
+
payload = {col_map.get(k, k): v for k, v in raw.items() if k != 'street_id'}
|
| 448 |
result = supabase.table("Positions").update(payload).eq("id", position_id).execute()
|
| 449 |
+
return Position(**MapsService._db_pos_to_model(result.data[0]))
|
| 450 |
except Exception as e:
|
| 451 |
raise Exception(f"Failed to update position: {str(e)}")
|
| 452 |
|
|
|
|
| 532 |
except Exception as e:
|
| 533 |
raise Exception(f"Failed to create/update positions bulk: {str(e)}")
|
| 534 |
|
| 535 |
+
# ==================== GROUPS ====================
|
| 536 |
+
@staticmethod
|
| 537 |
+
async def get_groups() -> List[Group]:
|
| 538 |
+
try:
|
| 539 |
+
result = supabase.table("Groups").select("*").order("id").execute()
|
| 540 |
+
return [Group(**item) for item in (result.data or [])]
|
| 541 |
+
except Exception as e:
|
| 542 |
+
raise Exception(f"Failed to fetch groups: {str(e)}")
|
| 543 |
+
|
| 544 |
+
@staticmethod
|
| 545 |
+
async def get_group_by_id(group_id: int) -> Optional[Group]:
|
| 546 |
+
try:
|
| 547 |
+
result = supabase.table("Groups").select("*").eq("id", group_id).limit(1).execute()
|
| 548 |
+
return Group(**result.data[0]) if result.data else None
|
| 549 |
+
except Exception as e:
|
| 550 |
+
raise Exception(f"Failed to fetch group: {str(e)}")
|
| 551 |
+
|
| 552 |
+
@staticmethod
|
| 553 |
+
async def create_group(data: GroupCreate) -> Group:
|
| 554 |
+
try:
|
| 555 |
+
next_id = _next_id("Groups")
|
| 556 |
+
payload = {
|
| 557 |
+
"id": next_id,
|
| 558 |
+
"lat_center": data.lat_center,
|
| 559 |
+
"long_center": data.long_center,
|
| 560 |
+
"streets": [int(s) for s in (data.streets or [])],
|
| 561 |
+
"neighbor": [int(n) for n in (data.neighbor or [])],
|
| 562 |
+
}
|
| 563 |
+
result = supabase.table("Groups").insert(payload).execute()
|
| 564 |
+
if not result.data:
|
| 565 |
+
raise Exception("Failed to insert group")
|
| 566 |
+
return Group(**result.data[0])
|
| 567 |
+
except Exception as e:
|
| 568 |
+
raise Exception(f"Failed to create group: {str(e)}")
|
| 569 |
+
|
| 570 |
+
@staticmethod
|
| 571 |
+
async def update_group(group_id: int, data: GroupUpdate) -> Group:
|
| 572 |
+
try:
|
| 573 |
+
payload = {}
|
| 574 |
+
for field in ("lat_center", "long_center", "streets", "neighbor"):
|
| 575 |
+
val = getattr(data, field)
|
| 576 |
+
if val is None:
|
| 577 |
+
continue
|
| 578 |
+
payload[field] = [int(x) for x in val] if field in ("streets", "neighbor") else val
|
| 579 |
+
if not payload:
|
| 580 |
+
raise Exception("No valid fields to update")
|
| 581 |
+
result = supabase.table("Groups").update(payload).eq("id", group_id).execute()
|
| 582 |
+
if not result.data:
|
| 583 |
+
raise Exception(f"Group #{group_id} not found or update failed")
|
| 584 |
+
return Group(**result.data[0])
|
| 585 |
+
except Exception as e:
|
| 586 |
+
raise Exception(f"Failed to update group: {str(e)}")
|
| 587 |
+
|
| 588 |
+
@staticmethod
|
| 589 |
+
async def delete_group(group_id: int) -> bool:
|
| 590 |
+
try:
|
| 591 |
+
supabase.table("Groups").delete().eq("id", group_id).execute()
|
| 592 |
+
return True
|
| 593 |
+
except Exception as e:
|
| 594 |
+
raise Exception(f"Failed to delete group: {str(e)}")
|
| 595 |
+
|
| 596 |
+
@staticmethod
|
| 597 |
+
async def update_poi(poi_id: int, fields: dict) -> dict:
|
| 598 |
+
"""Update a single Pois row with the provided fields (lat/long/name/address/type).
|
| 599 |
+
Returns the updated row.
|
| 600 |
+
"""
|
| 601 |
+
try:
|
| 602 |
+
allowed = {"lat", "long", "name", "address", "type", "city_id", "district_id"}
|
| 603 |
+
payload = {k: v for k, v in (fields or {}).items() if k in allowed and v is not None}
|
| 604 |
+
if not payload:
|
| 605 |
+
raise Exception("No valid fields to update")
|
| 606 |
+
result = supabase.table("Pois").update(payload).eq("id", poi_id).execute()
|
| 607 |
+
if not result.data:
|
| 608 |
+
raise Exception(f"POI #{poi_id} not found or update failed")
|
| 609 |
+
return result.data[0]
|
| 610 |
+
except Exception as e:
|
| 611 |
+
raise Exception(f"Failed to update POI: {str(e)}")
|
| 612 |
+
|
| 613 |
+
@staticmethod
|
| 614 |
+
async def update_pois_bulk(payload: list) -> list:
|
| 615 |
+
"""payload: [{poi_id, lat?, long?, name?, address?, type?}, ...]
|
| 616 |
+
Updates each POI by id. Returns list of updated rows.
|
| 617 |
+
"""
|
| 618 |
+
updated = []
|
| 619 |
+
allowed = {"lat", "long", "name", "address", "type", "city_id", "district_id"}
|
| 620 |
+
try:
|
| 621 |
+
for item in payload or []:
|
| 622 |
+
poi_id = item.get("poi_id") or item.get("id")
|
| 623 |
+
if not poi_id:
|
| 624 |
+
continue
|
| 625 |
+
fields = {k: v for k, v in item.items() if k in allowed and v is not None}
|
| 626 |
+
if not fields:
|
| 627 |
+
continue
|
| 628 |
+
result = supabase.table("Pois").update(fields).eq("id", int(poi_id)).execute()
|
| 629 |
+
if result.data:
|
| 630 |
+
updated.append(result.data[0])
|
| 631 |
+
return updated
|
| 632 |
+
except Exception as e:
|
| 633 |
+
raise Exception(f"Failed to update POIs bulk: {str(e)}")
|
| 634 |
+
|
| 635 |
+
# Path where position.bin is cached on disk (backend/position.bin)
|
| 636 |
+
_BIN_PATH: str = ""
|
| 637 |
+
|
| 638 |
+
@classmethod
|
| 639 |
+
def _bin_path(cls) -> str:
|
| 640 |
+
if not cls._BIN_PATH:
|
| 641 |
+
import os
|
| 642 |
+
cls._BIN_PATH = os.path.join(
|
| 643 |
+
os.path.dirname(os.path.abspath(__file__)), '..', 'position.bin'
|
| 644 |
+
)
|
| 645 |
+
return cls._BIN_PATH
|
| 646 |
+
|
| 647 |
+
@staticmethod
|
| 648 |
+
async def export_graph_bin() -> bytes:
|
| 649 |
+
"""Serialize the position graph as a compact binary file for A* pathfinding.
|
| 650 |
+
|
| 651 |
+
Format (little-endian):
|
| 652 |
+
HEADER (16 bytes): magic b"POSBIN\x01\x00" (8B) | node_count uint32 | edge_count uint32
|
| 653 |
+
NODES (4B x node_count): sorted node ids as int32
|
| 654 |
+
EDGES (12B x edge_count): from_id int32 | to_id int32 | weight_m float32
|
| 655 |
+
edges are undirected and stored with from_id < to_id.
|
| 656 |
+
|
| 657 |
+
Nodes contain id only — client maps back to DB for lat/lon/name.
|
| 658 |
+
Also writes the file to disk at backend/position.bin for pathfind() to consume.
|
| 659 |
+
"""
|
| 660 |
+
import math
|
| 661 |
+
import struct
|
| 662 |
+
|
| 663 |
+
def haversine_m(lat1, lon1, lat2, lon2):
|
| 664 |
+
R = 6_371_000
|
| 665 |
+
phi1, phi2 = math.radians(lat1), math.radians(lat2)
|
| 666 |
+
dphi = math.radians(lat2 - lat1)
|
| 667 |
+
dlam = math.radians(lon2 - lon1)
|
| 668 |
+
a = math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlam / 2) ** 2
|
| 669 |
+
return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
| 670 |
+
|
| 671 |
+
try:
|
| 672 |
+
result = supabase.table("Positions").select("id, lat, long, streets").execute()
|
| 673 |
+
all_positions = result.data or []
|
| 674 |
+
|
| 675 |
+
# Index valid positions (must have lat and long)
|
| 676 |
+
pos_by_id: dict = {}
|
| 677 |
+
for p in all_positions:
|
| 678 |
+
if p.get("lat") is not None and p.get("long") is not None:
|
| 679 |
+
pos_by_id[p["id"]] = p
|
| 680 |
+
|
| 681 |
+
# Build street → list of position ids
|
| 682 |
+
street_to_pids: dict = {}
|
| 683 |
+
for pid, pos in pos_by_id.items():
|
| 684 |
+
for sid in (pos.get("streets") or []):
|
| 685 |
+
street_to_pids.setdefault(sid, []).append(pid)
|
| 686 |
+
|
| 687 |
+
# Build edges: undirected, from_id < to_id, keep minimum weight per pair
|
| 688 |
+
edge_map: dict = {} # (from_id, to_id) → weight_m
|
| 689 |
+
for sid, pids in street_to_pids.items():
|
| 690 |
+
for i in range(len(pids)):
|
| 691 |
+
for j in range(i + 1, len(pids)):
|
| 692 |
+
a_id, b_id = pids[i], pids[j]
|
| 693 |
+
if a_id > b_id:
|
| 694 |
+
a_id, b_id = b_id, a_id
|
| 695 |
+
pa = pos_by_id[a_id]
|
| 696 |
+
pb = pos_by_id[b_id]
|
| 697 |
+
w = haversine_m(
|
| 698 |
+
float(pa["lat"]), float(pa["long"]),
|
| 699 |
+
float(pb["lat"]), float(pb["long"])
|
| 700 |
+
)
|
| 701 |
+
key = (a_id, b_id)
|
| 702 |
+
if key not in edge_map or w < edge_map[key]:
|
| 703 |
+
edge_map[key] = w
|
| 704 |
+
|
| 705 |
+
sorted_node_ids = sorted(pos_by_id.keys())
|
| 706 |
+
edges = [(a, b, w) for (a, b), w in edge_map.items()]
|
| 707 |
+
|
| 708 |
+
node_count = len(sorted_node_ids)
|
| 709 |
+
edge_count = len(edges)
|
| 710 |
+
|
| 711 |
+
buf = bytearray()
|
| 712 |
+
buf += struct.pack('<8sII', b"POSBIN\x01\x00", node_count, edge_count)
|
| 713 |
+
if node_count:
|
| 714 |
+
buf += struct.pack(f'<{node_count}i', *sorted_node_ids)
|
| 715 |
+
for a_id, b_id, w in edges:
|
| 716 |
+
buf += struct.pack('<iif', a_id, b_id, w)
|
| 717 |
+
|
| 718 |
+
data = bytes(buf)
|
| 719 |
+
|
| 720 |
+
# Save to disk so pathfind() can consume it without re-fetching DB
|
| 721 |
+
with open(MapsService._bin_path(), 'wb') as f:
|
| 722 |
+
f.write(data)
|
| 723 |
+
|
| 724 |
+
return data
|
| 725 |
+
except Exception as e:
|
| 726 |
+
raise Exception(f"Failed to export graph binary: {str(e)}")
|
| 727 |
+
|
| 728 |
+
@staticmethod
|
| 729 |
+
async def pathfind(start_id: int, end_id: int) -> dict:
|
| 730 |
+
"""Run Dijkstra on the pre-built position graph stored in position.bin.
|
| 731 |
+
|
| 732 |
+
Graph is loaded from the binary file (no full DB fetch).
|
| 733 |
+
Only queries DB for: street arrays of nodes in the found path + street details.
|
| 734 |
+
Returns node path, ordered street ids, street details, and total distance.
|
| 735 |
+
|
| 736 |
+
Requires position.bin to exist — call GET /export/position.bin first to generate it.
|
| 737 |
+
"""
|
| 738 |
+
import struct
|
| 739 |
+
import heapq
|
| 740 |
+
import os
|
| 741 |
+
|
| 742 |
+
bin_path = MapsService._bin_path()
|
| 743 |
+
if not os.path.exists(bin_path):
|
| 744 |
+
raise Exception(
|
| 745 |
+
"position.bin not found. Call GET /export/position.bin first to generate the graph file."
|
| 746 |
+
)
|
| 747 |
+
|
| 748 |
+
try:
|
| 749 |
+
with open(bin_path, 'rb') as f:
|
| 750 |
+
data = f.read()
|
| 751 |
+
|
| 752 |
+
# --- Parse header ---
|
| 753 |
+
magic, node_count, edge_count = struct.unpack_from('<8sII', data, 0)
|
| 754 |
+
if magic != b"POSBIN\x01\x00":
|
| 755 |
+
raise Exception("Invalid position.bin format — unexpected magic bytes")
|
| 756 |
+
|
| 757 |
+
# --- Parse node ids ---
|
| 758 |
+
node_ids: list = list(struct.unpack_from(f'<{node_count}i', data, 16))
|
| 759 |
+
node_set: set = set(node_ids)
|
| 760 |
+
|
| 761 |
+
if start_id not in node_set:
|
| 762 |
+
raise Exception(f"Start position #{start_id} not found in graph")
|
| 763 |
+
if end_id not in node_set:
|
| 764 |
+
raise Exception(f"End position #{end_id} not found in graph")
|
| 765 |
+
if start_id == end_id:
|
| 766 |
+
return {"found": True, "node_path": [start_id], "street_path": [], "street_details": [], "total_m": 0}
|
| 767 |
+
|
| 768 |
+
# --- Parse edges → build undirected adjacency ---
|
| 769 |
+
adjacency: dict = {nid: {} for nid in node_ids}
|
| 770 |
+
edge_offset = 16 + node_count * 4
|
| 771 |
+
for i in range(edge_count):
|
| 772 |
+
a, b, w = struct.unpack_from('<iif', data, edge_offset + i * 12)
|
| 773 |
+
# bin stores from_id < to_id; mirror both directions
|
| 774 |
+
if b not in adjacency[a] or adjacency[a][b] > w:
|
| 775 |
+
adjacency[a][b] = w
|
| 776 |
+
adjacency[b][a] = w
|
| 777 |
+
|
| 778 |
+
# --- Dijkstra (no lat/lon in bin → no heuristic needed) ---
|
| 779 |
+
g: dict = {nid: float("inf") for nid in node_ids}
|
| 780 |
+
g[start_id] = 0.0
|
| 781 |
+
prev: dict = {nid: None for nid in node_ids}
|
| 782 |
+
heap = [(0.0, start_id)]
|
| 783 |
+
|
| 784 |
+
while heap:
|
| 785 |
+
cost, u = heapq.heappop(heap)
|
| 786 |
+
if u == end_id:
|
| 787 |
+
break
|
| 788 |
+
if cost > g[u]:
|
| 789 |
+
continue
|
| 790 |
+
for v, w in adjacency[u].items():
|
| 791 |
+
ng = g[u] + w
|
| 792 |
+
if ng < g[v]:
|
| 793 |
+
g[v] = ng
|
| 794 |
+
prev[v] = u
|
| 795 |
+
heapq.heappush(heap, (ng, v))
|
| 796 |
+
|
| 797 |
+
if g[end_id] == float("inf"):
|
| 798 |
+
return {"found": False, "node_path": [], "street_path": [], "street_details": [], "total_m": 0}
|
| 799 |
+
|
| 800 |
+
# --- Reconstruct node path ---
|
| 801 |
+
path: list = []
|
| 802 |
+
cur = end_id
|
| 803 |
+
while cur is not None:
|
| 804 |
+
path.append(cur)
|
| 805 |
+
cur = prev[cur]
|
| 806 |
+
path.reverse()
|
| 807 |
+
|
| 808 |
+
# --- Minimal DB query: only fetch streets array for nodes in path ---
|
| 809 |
+
pos_result = supabase.table("Positions") \
|
| 810 |
+
.select("id, streets") \
|
| 811 |
+
.in_("id", path).execute()
|
| 812 |
+
pos_streets: dict = {
|
| 813 |
+
p["id"]: (p.get("streets") or [])
|
| 814 |
+
for p in (pos_result.data or [])
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
# --- Extract ordered unique street IDs from consecutive node pairs ---
|
| 818 |
+
street_path: list = []
|
| 819 |
+
seen_streets: set = set()
|
| 820 |
+
for i in range(len(path) - 1):
|
| 821 |
+
shared = (set(pos_streets.get(path[i], [])) &
|
| 822 |
+
set(pos_streets.get(path[i + 1], [])))
|
| 823 |
+
for sid in shared:
|
| 824 |
+
if sid not in seen_streets:
|
| 825 |
+
seen_streets.add(sid)
|
| 826 |
+
street_path.append(sid)
|
| 827 |
+
|
| 828 |
+
# --- Fetch street details for the path ---
|
| 829 |
+
street_details: list = []
|
| 830 |
+
if street_path:
|
| 831 |
+
sr = supabase.table("Streets") \
|
| 832 |
+
.select("id, name, type, district_id, city_id") \
|
| 833 |
+
.in_("id", street_path).execute()
|
| 834 |
+
street_map = {s["id"]: s for s in (sr.data or [])}
|
| 835 |
+
street_details = [street_map[sid] for sid in street_path if sid in street_map]
|
| 836 |
+
|
| 837 |
+
return {
|
| 838 |
+
"found": True,
|
| 839 |
+
"node_path": path,
|
| 840 |
+
"street_path": street_path,
|
| 841 |
+
"street_details": street_details,
|
| 842 |
+
"total_m": round(g[end_id]),
|
| 843 |
+
}
|
| 844 |
+
except Exception as e:
|
| 845 |
+
raise Exception(f"Pathfind failed: {str(e)}")
|
| 846 |
+
|
| 847 |
+
@staticmethod
|
| 848 |
+
async def nearest_position(qlat: float, qlon: float, top_k: int = 3) -> dict:
|
| 849 |
+
"""Find the single nearest position to (qlat, qlon) with a 2-step Group strategy:
|
| 850 |
+
1) pick the `top_k` nearest Groups by (lat_center, long_center)
|
| 851 |
+
2) load ONLY those groups' positions (via Streets.positions) — not the whole DB
|
| 852 |
+
3) find the nearest position inside EACH group
|
| 853 |
+
4) return the closest among the per-group winners
|
| 854 |
+
Falls back to a full DB scan when there are no groups / no group positions.
|
| 855 |
+
Returns: {found, position: {id,lat,lon,streets,street_names}|None, distance_m, group_id}
|
| 856 |
+
"""
|
| 857 |
+
import math
|
| 858 |
+
|
| 859 |
+
def haversine_m(lat1, lon1, lat2, lon2):
|
| 860 |
+
R = 6_371_000
|
| 861 |
+
phi1, phi2 = math.radians(lat1), math.radians(lat2)
|
| 862 |
+
dphi = math.radians(lat2 - lat1)
|
| 863 |
+
dlam = math.radians(lon2 - lon1)
|
| 864 |
+
a = math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlam / 2) ** 2
|
| 865 |
+
return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
| 866 |
+
|
| 867 |
+
best_pos, best_d, best_gid = None, float("inf"), None
|
| 868 |
+
|
| 869 |
+
# 1) Nearest `top_k` groups by their center
|
| 870 |
+
groups_res = supabase.table("Groups").select("id, lat_center, long_center, streets").execute()
|
| 871 |
+
groups = [g for g in (groups_res.data or [])
|
| 872 |
+
if g.get("lat_center") is not None and g.get("long_center") is not None]
|
| 873 |
+
groups.sort(key=lambda g: haversine_m(qlat, qlon, float(g["lat_center"]), float(g["long_center"])))
|
| 874 |
+
nearest_groups = groups[:top_k]
|
| 875 |
+
|
| 876 |
+
# 2) Group streets → position ids (via Streets.positions); batch-load ONLY those positions
|
| 877 |
+
all_street_ids = sorted({int(s) for g in nearest_groups for s in (g.get("streets") or [])})
|
| 878 |
+
if all_street_ids:
|
| 879 |
+
sres = supabase.table("Streets").select("id, positions").in_("id", all_street_ids).execute()
|
| 880 |
+
street_positions = {s["id"]: [int(p) for p in (s.get("positions") or [])] for s in (sres.data or [])}
|
| 881 |
+
group_pos_ids: dict = {}
|
| 882 |
+
all_pos_ids: set = set()
|
| 883 |
+
for g in nearest_groups:
|
| 884 |
+
pids = set()
|
| 885 |
+
for sid in (g.get("streets") or []):
|
| 886 |
+
pids.update(street_positions.get(int(sid), []))
|
| 887 |
+
group_pos_ids[g["id"]] = pids
|
| 888 |
+
all_pos_ids.update(pids)
|
| 889 |
+
if all_pos_ids:
|
| 890 |
+
pres = supabase.table("Positions").select("*").in_("id", list(all_pos_ids)).execute()
|
| 891 |
+
pos_by_id = {p["id"]: p for p in (pres.data or [])
|
| 892 |
+
if p.get("lat") is not None and p.get("long") is not None}
|
| 893 |
+
# 3) nearest position inside each group + 4) compare the winners
|
| 894 |
+
for g in nearest_groups:
|
| 895 |
+
g_pos, g_d = None, float("inf")
|
| 896 |
+
for pid in group_pos_ids.get(g["id"], ()): # chỉ xét position của group này
|
| 897 |
+
pos = pos_by_id.get(pid)
|
| 898 |
+
if not pos:
|
| 899 |
+
continue
|
| 900 |
+
d = haversine_m(qlat, qlon, float(pos["lat"]), float(pos["long"]))
|
| 901 |
+
if d < g_d:
|
| 902 |
+
g_d, g_pos = d, pos
|
| 903 |
+
if g_pos is not None and g_d < best_d:
|
| 904 |
+
best_d, best_pos, best_gid = g_d, g_pos, g["id"]
|
| 905 |
+
|
| 906 |
+
# Fallback: không có group / group rỗng → quét toàn bộ DB (đúng tuyệt đối)
|
| 907 |
+
if best_pos is None:
|
| 908 |
+
res = supabase.table("Positions").select("id, lat, long, streets").execute()
|
| 909 |
+
for p in (res.data or []):
|
| 910 |
+
if p.get("lat") is None or p.get("long") is None:
|
| 911 |
+
continue
|
| 912 |
+
d = haversine_m(qlat, qlon, float(p["lat"]), float(p["long"]))
|
| 913 |
+
if d < best_d:
|
| 914 |
+
best_d, best_pos, best_gid = d, p, None
|
| 915 |
+
|
| 916 |
+
if best_pos is None:
|
| 917 |
+
return {"found": False, "position": None, "distance_m": None, "group_id": None}
|
| 918 |
+
|
| 919 |
+
# Resolve street names for the chosen position
|
| 920 |
+
street_ids = best_pos.get("streets") or []
|
| 921 |
+
smap = {}
|
| 922 |
+
if street_ids:
|
| 923 |
+
sr = supabase.table("Streets").select("id, name").in_("id", street_ids).execute()
|
| 924 |
+
smap = {s["id"]: s["name"] for s in (sr.data or [])}
|
| 925 |
+
position = {
|
| 926 |
+
"id": best_pos["id"],
|
| 927 |
+
"lat": best_pos["lat"],
|
| 928 |
+
"lon": best_pos["long"],
|
| 929 |
+
"streets": street_ids,
|
| 930 |
+
"street_names": [smap.get(sid, str(sid)) for sid in street_ids],
|
| 931 |
+
}
|
| 932 |
+
return {"found": True, "position": position, "distance_m": round(best_d), "group_id": best_gid}
|
| 933 |
+
|
| 934 |
@staticmethod
|
| 935 |
async def search_nearest(points: list) -> dict:
|
| 936 |
"""
|
|
|
|
| 1038 |
return ". ".join(instructions) + "."
|
| 1039 |
|
| 1040 |
try:
|
| 1041 |
+
# ── Single-point: reuse the extracted 2-step group-based nearest search ──
|
| 1042 |
+
if len(points) < 2:
|
| 1043 |
+
res = await MapsService.nearest_position(float(points[0]["lat"]), float(points[0]["lon"]))
|
| 1044 |
+
if not res.get("found"):
|
| 1045 |
+
return {"data": [], "label": "Không có điểm nào trong CSDL", "tts": "", "warning": "", "message": ""}
|
| 1046 |
+
pt, dist_m, gid = res["position"], res["distance_m"], res["group_id"]
|
| 1047 |
+
sname = pt["street_names"][0] if pt["street_names"] else "vị trí không xác định"
|
| 1048 |
+
dl = f"~{dist_m}m" if dist_m < 1000 else f"~{round(dist_m / 1000, 1)}km"
|
| 1049 |
+
gtxt = f" | Group #{gid}" if gid is not None else ""
|
| 1050 |
+
return {
|
| 1051 |
+
"data": [pt],
|
| 1052 |
+
"label": f"Điểm gần nhất: {sname} ({dl})",
|
| 1053 |
+
"tts": f"Vị trí gần nhất nằm trên {sname}, cách bạn khoảng {dist_m} mét.",
|
| 1054 |
+
"warning": "",
|
| 1055 |
+
"message": f"Khoảng cách: ~{dist_m}m{gtxt}",
|
| 1056 |
+
}
|
| 1057 |
+
|
| 1058 |
result = supabase.table("Positions").select("*").execute()
|
| 1059 |
all_positions = result.data or []
|
| 1060 |
if not all_positions:
|
|
|
|
| 1090 |
return f"~{m}m" if m < 1000 else f"~{round(m / 1000, 1)}km"
|
| 1091 |
|
| 1092 |
# ── Single-point mode: return nearest ────────────────────────────
|
| 1093 |
+
# (single-point handled earlier via MapsService.nearest_position)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
# ── Two-point mode: shortest path via Dijkstra ───────────────────
|
| 1096 |
q1, q2 = points[0], points[1]
|
|
|
|
| 1186 |
except Exception as e:
|
| 1187 |
raise Exception(f"Search nearest failed: {str(e)}")
|
| 1188 |
|
| 1189 |
+
@staticmethod
|
| 1190 |
+
async def search_uuid(query_id: str) -> dict:
|
| 1191 |
+
"""Search the utest table for the row whose id is most similar to query_id.
|
| 1192 |
+
Uses Levenshtein distance (normalised) as similarity score.
|
| 1193 |
+
Returns: {found, id, score, row} where score=0.0 is exact match.
|
| 1194 |
+
"""
|
| 1195 |
+
def _lev(a: str, b: str) -> int:
|
| 1196 |
+
if a == b:
|
| 1197 |
+
return 0
|
| 1198 |
+
if not a:
|
| 1199 |
+
return len(b)
|
| 1200 |
+
if not b:
|
| 1201 |
+
return len(a)
|
| 1202 |
+
prev = list(range(len(b) + 1))
|
| 1203 |
+
for i, ca in enumerate(a, start=1):
|
| 1204 |
+
curr = [i]
|
| 1205 |
+
for j, cb in enumerate(b, start=1):
|
| 1206 |
+
curr.append(min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + (0 if ca == cb else 1)))
|
| 1207 |
+
prev = curr
|
| 1208 |
+
return prev[-1]
|
| 1209 |
+
|
| 1210 |
+
try:
|
| 1211 |
+
result = supabase.table("utest").select("*").execute()
|
| 1212 |
+
rows = result.data or []
|
| 1213 |
+
if not rows:
|
| 1214 |
+
return {"found": False, "id": None, "score": None, "row": None}
|
| 1215 |
+
|
| 1216 |
+
q = str(query_id).strip()
|
| 1217 |
+
best_row = None
|
| 1218 |
+
best_score = float("inf")
|
| 1219 |
+
|
| 1220 |
+
for row in rows:
|
| 1221 |
+
row_id = str(row.get("id") or "").strip()
|
| 1222 |
+
if not row_id:
|
| 1223 |
+
continue
|
| 1224 |
+
dist = _lev(q, row_id)
|
| 1225 |
+
max_len = max(len(q), len(row_id), 1)
|
| 1226 |
+
score = dist / max_len
|
| 1227 |
+
if score < best_score:
|
| 1228 |
+
best_score = score
|
| 1229 |
+
best_row = row
|
| 1230 |
+
|
| 1231 |
+
if best_row is None:
|
| 1232 |
+
return {"found": False, "id": None, "score": None, "row": None}
|
| 1233 |
+
|
| 1234 |
+
return {
|
| 1235 |
+
"found": True,
|
| 1236 |
+
"id": best_row.get("id"),
|
| 1237 |
+
"score": round(best_score, 4),
|
| 1238 |
+
"row": best_row,
|
| 1239 |
+
}
|
| 1240 |
+
except Exception as e:
|
| 1241 |
+
raise Exception(f"search_uuid failed: {str(e)}")
|
| 1242 |
+
|
| 1243 |
@staticmethod
|
| 1244 |
async def delete_position_cascade(position_id: int) -> bool:
|
| 1245 |
"""Delete a position and remove its id from any Streets.positions arrays listed in position.streets"""
|