update tile endpoints to the Martin host, refine map styling and snapping logic, and add geolocation-based map centering.
This commit is contained in:
+256
-35
@@ -59,13 +59,25 @@
|
|||||||
},
|
},
|
||||||
"overture_buildings": {
|
"overture_buildings": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"tiles": ["https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}"],
|
"tiles": [
|
||||||
|
"https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}"
|
||||||
|
],
|
||||||
"maxzoom": 14
|
"maxzoom": 14
|
||||||
},
|
},
|
||||||
"overture_segments": {
|
"overture_segments": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"tiles": ["https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"],
|
"tiles": [
|
||||||
|
"https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"
|
||||||
|
],
|
||||||
"maxzoom": 14
|
"maxzoom": 14
|
||||||
|
},
|
||||||
|
"approved_roads": {
|
||||||
|
"type": "vector",
|
||||||
|
"tiles": [
|
||||||
|
"http://188.68.36.205:3202/approved_roads/{z}/{x}/{y}"
|
||||||
|
],
|
||||||
|
"minzoom": 8,
|
||||||
|
"maxzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layers": [
|
"layers": [
|
||||||
@@ -599,7 +611,7 @@
|
|||||||
"track",
|
"track",
|
||||||
"path",
|
"path",
|
||||||
"footway",
|
"footway",
|
||||||
"cycleway",
|
"cycleway",
|
||||||
"steps"
|
"steps"
|
||||||
],
|
],
|
||||||
"minzoom": 14,
|
"minzoom": 14,
|
||||||
@@ -687,6 +699,59 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-casing",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#B9C2CE",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
2.2,
|
||||||
|
16,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"line-opacity": 0.9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-core",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#FFFFFF",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
0.8,
|
||||||
|
16,
|
||||||
|
8
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "road-casing-tertiary",
|
"id": "road-casing-tertiary",
|
||||||
"type": "line",
|
"type": "line",
|
||||||
@@ -927,15 +992,44 @@
|
|||||||
"minzoom": 15,
|
"minzoom": 15,
|
||||||
"filter": [
|
"filter": [
|
||||||
"all",
|
"all",
|
||||||
["in", "highway", "motorway", "trunk", "primary", "secondary", "tertiary", "residential", "unclassified", "living_street"],
|
[
|
||||||
["==", "oneway", "yes"]
|
"in",
|
||||||
|
"highway",
|
||||||
|
"motorway",
|
||||||
|
"trunk",
|
||||||
|
"primary",
|
||||||
|
"secondary",
|
||||||
|
"tertiary",
|
||||||
|
"residential",
|
||||||
|
"unclassified",
|
||||||
|
"living_street"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"==",
|
||||||
|
"oneway",
|
||||||
|
"yes"
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
"symbol-placement": "line",
|
"symbol-placement": "line",
|
||||||
"symbol-spacing": 80,
|
"symbol-spacing": 80,
|
||||||
"text-field": "▸",
|
"text-field": "▸",
|
||||||
"text-font": ["Noto Sans Regular"],
|
"text-font": [
|
||||||
"text-size": ["interpolate", ["linear"], ["zoom"], 15, 12, 18, 18],
|
"Noto Sans Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
15,
|
||||||
|
12,
|
||||||
|
18,
|
||||||
|
18
|
||||||
|
],
|
||||||
"text-keep-upright": false,
|
"text-keep-upright": false,
|
||||||
"text-rotation-alignment": "map",
|
"text-rotation-alignment": "map",
|
||||||
"text-allow-overlap": true,
|
"text-allow-overlap": true,
|
||||||
@@ -943,7 +1037,19 @@
|
|||||||
"text-padding": 0
|
"text-padding": 0
|
||||||
},
|
},
|
||||||
"paint": {
|
"paint": {
|
||||||
"text-color": ["interpolate", ["linear"], ["zoom"], 15, "rgba(100,116,139,0.4)", 18, "rgba(100,116,139,0.7)"],
|
"text-color": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
15,
|
||||||
|
"rgba(100,116,139,0.4)",
|
||||||
|
18,
|
||||||
|
"rgba(100,116,139,0.7)"
|
||||||
|
],
|
||||||
"text-halo-color": "rgba(255,255,255,0.3)",
|
"text-halo-color": "rgba(255,255,255,0.3)",
|
||||||
"text-halo-width": 0.5
|
"text-halo-width": 0.5
|
||||||
}
|
}
|
||||||
@@ -977,21 +1083,54 @@
|
|||||||
"fill-extrusion-color": "#DDD8D0",
|
"fill-extrusion-color": "#DDD8D0",
|
||||||
"fill-extrusion-height": [
|
"fill-extrusion-height": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
["linear"],
|
[
|
||||||
["zoom"],
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
14,
|
14,
|
||||||
[
|
[
|
||||||
"*",
|
"*",
|
||||||
["coalesce", ["to-number", ["get", "num_floors"], null], 3],
|
[
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"to-number",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"num_floors"
|
||||||
|
],
|
||||||
|
null
|
||||||
|
],
|
||||||
|
3
|
||||||
|
],
|
||||||
2.5
|
2.5
|
||||||
],
|
],
|
||||||
17,
|
17,
|
||||||
[
|
[
|
||||||
"coalesce",
|
"coalesce",
|
||||||
["to-number", ["get", "height"], null],
|
[
|
||||||
|
"to-number",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"height"
|
||||||
|
],
|
||||||
|
null
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"*",
|
"*",
|
||||||
["coalesce", ["to-number", ["get", "num_floors"], null], 3],
|
[
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"to-number",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"num_floors"
|
||||||
|
],
|
||||||
|
null
|
||||||
|
],
|
||||||
|
3
|
||||||
|
],
|
||||||
3.5
|
3.5
|
||||||
],
|
],
|
||||||
12
|
12
|
||||||
@@ -1000,10 +1139,16 @@
|
|||||||
"fill-extrusion-base": 0,
|
"fill-extrusion-base": 0,
|
||||||
"fill-extrusion-opacity": [
|
"fill-extrusion-opacity": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
["linear"],
|
[
|
||||||
["zoom"],
|
"linear"
|
||||||
14, 0.55,
|
],
|
||||||
16, 0.85
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
14,
|
||||||
|
0.55,
|
||||||
|
16,
|
||||||
|
0.85
|
||||||
],
|
],
|
||||||
"fill-extrusion-vertical-gradient": true
|
"fill-extrusion-vertical-gradient": true
|
||||||
}
|
}
|
||||||
@@ -1178,12 +1323,32 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"text-field": [
|
"text-field": [
|
||||||
"coalesce",
|
"coalesce",
|
||||||
["get", "name:ar"],
|
[
|
||||||
["get", "name"],
|
"get",
|
||||||
|
"name:ar"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"text-font": ["Noto Sans Regular"],
|
"text-font": [
|
||||||
"text-size": ["interpolate", ["linear"], ["zoom"], 15, 10, 18, 13],
|
"Noto Sans Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
18,
|
||||||
|
13
|
||||||
|
],
|
||||||
"symbol-placement": "line",
|
"symbol-placement": "line",
|
||||||
"text-letter-spacing": 0.05,
|
"text-letter-spacing": 0.05,
|
||||||
"text-padding": 15,
|
"text-padding": 15,
|
||||||
@@ -1216,12 +1381,34 @@
|
|||||||
"layout": {
|
"layout": {
|
||||||
"text-field": [
|
"text-field": [
|
||||||
"coalesce",
|
"coalesce",
|
||||||
["get", "name:ar"],
|
[
|
||||||
["get", "name"],
|
"get",
|
||||||
|
"name:ar"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"text-font": ["Noto Sans Bold"],
|
"text-font": [
|
||||||
"text-size": ["interpolate", ["linear"], ["zoom"], 12, 10, 14, 13, 18, 16],
|
"Noto Sans Bold"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
14,
|
||||||
|
13,
|
||||||
|
18,
|
||||||
|
16
|
||||||
|
],
|
||||||
"symbol-placement": "line",
|
"symbol-placement": "line",
|
||||||
"text-letter-spacing": 0.06,
|
"text-letter-spacing": 0.06,
|
||||||
"text-padding": 20,
|
"text-padding": 20,
|
||||||
@@ -1445,7 +1632,7 @@
|
|||||||
""
|
""
|
||||||
],
|
],
|
||||||
"text-font": [
|
"text-font": [
|
||||||
"Noto Sans Regular"
|
"Noto Sans Regular"
|
||||||
],
|
],
|
||||||
"text-size": 11,
|
"text-size": 11,
|
||||||
"text-offset": [
|
"text-offset": [
|
||||||
@@ -1550,7 +1737,7 @@
|
|||||||
""
|
""
|
||||||
],
|
],
|
||||||
"text-font": [
|
"text-font": [
|
||||||
"Noto Sans Regular"
|
"Noto Sans Regular"
|
||||||
],
|
],
|
||||||
"text-size": [
|
"text-size": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
@@ -1730,7 +1917,7 @@
|
|||||||
"text-size": [
|
"text-size": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
[
|
[
|
||||||
"linear"
|
"linear"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"zoom"
|
"zoom"
|
||||||
@@ -1760,7 +1947,11 @@
|
|||||||
"source": "places_syria",
|
"source": "places_syria",
|
||||||
"source-layer": "places_syria",
|
"source-layer": "places_syria",
|
||||||
"minzoom": 10,
|
"minzoom": 10,
|
||||||
"filter": ["!in", "category", "street"],
|
"filter": [
|
||||||
|
"!in",
|
||||||
|
"category",
|
||||||
|
"street"
|
||||||
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
"text-field": [
|
"text-field": [
|
||||||
"coalesce",
|
"coalesce",
|
||||||
@@ -1810,7 +2001,11 @@
|
|||||||
"source": "places_jordan",
|
"source": "places_jordan",
|
||||||
"source-layer": "places_jordan",
|
"source-layer": "places_jordan",
|
||||||
"minzoom": 10,
|
"minzoom": 10,
|
||||||
"filter": ["!in", "category", "street"],
|
"filter": [
|
||||||
|
"!in",
|
||||||
|
"category",
|
||||||
|
"street"
|
||||||
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
"text-field": [
|
"text-field": [
|
||||||
"coalesce",
|
"coalesce",
|
||||||
@@ -1905,11 +2100,37 @@
|
|||||||
"source": "overture_segments",
|
"source": "overture_segments",
|
||||||
"source-layer": "overture_segment",
|
"source-layer": "overture_segment",
|
||||||
"minzoom": 14,
|
"minzoom": 14,
|
||||||
"filter": ["has", "names"],
|
"filter": [
|
||||||
|
"has",
|
||||||
|
"names"
|
||||||
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
"text-field": ["coalesce", ["get", "names"], ""],
|
"text-field": [
|
||||||
"text-font": ["Noto Sans Regular"],
|
"coalesce",
|
||||||
"text-size": ["interpolate", ["linear"], ["zoom"], 14, 9, 16, 12, 18, 14],
|
[
|
||||||
|
"get",
|
||||||
|
"names"
|
||||||
|
],
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"Noto Sans Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
14,
|
||||||
|
9,
|
||||||
|
16,
|
||||||
|
12,
|
||||||
|
18,
|
||||||
|
14
|
||||||
|
],
|
||||||
"symbol-placement": "line",
|
"symbol-placement": "line",
|
||||||
"text-rotation-alignment": "map",
|
"text-rotation-alignment": "map",
|
||||||
"text-pitch-alignment": "viewport",
|
"text-pitch-alignment": "viewport",
|
||||||
@@ -1928,4 +2149,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
const tilesUrl = (import.meta as any).env.VITE_TILES_URL || 'https://tiles.intaleqapp.com';
|
const tilesUrl = (import.meta as any).env.VITE_TILES_URL || 'https://tiles.intaleqapp.com';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
map.current = new maplibregl.Map({
|
const initialMap = new maplibregl.Map({
|
||||||
container: mapContainer.current,
|
container: mapContainer.current,
|
||||||
style: {
|
style: {
|
||||||
version: 8,
|
version: 8,
|
||||||
@@ -75,6 +75,12 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
type: 'vector',
|
type: 'vector',
|
||||||
tiles: [`${tilesUrl}/planet_osm_point/{z}/{x}/{y}`],
|
tiles: [`${tilesUrl}/planet_osm_point/{z}/{x}/{y}`],
|
||||||
maxzoom: 14
|
maxzoom: 14
|
||||||
|
},
|
||||||
|
'approved_roads': {
|
||||||
|
type: 'vector',
|
||||||
|
tiles: [`${tilesUrl}/approved_roads/{z}/{x}/{y}`],
|
||||||
|
minzoom: 8,
|
||||||
|
maxzoom: 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layers: [
|
layers: [
|
||||||
@@ -145,6 +151,26 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
'line-width': ['interpolate', ['linear'], ['zoom'], 13, 1, 16, 8] as any
|
'line-width': ['interpolate', ['linear'], ['zoom'], 13, 1, 16, 8] as any
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'approved-casing',
|
||||||
|
type: 'line',
|
||||||
|
source: 'approved_roads',
|
||||||
|
'source-layer': 'approved_roads',
|
||||||
|
paint: {
|
||||||
|
'line-color': '#d4d4d4',
|
||||||
|
'line-width': ['interpolate', ['linear'], ['zoom'], 13, 1, 16, 8] as any
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'approved-core',
|
||||||
|
type: 'line',
|
||||||
|
source: 'approved_roads',
|
||||||
|
'source-layer': 'approved_roads',
|
||||||
|
paint: {
|
||||||
|
'line-color': '#ffffff',
|
||||||
|
'line-width': ['interpolate', ['linear'], ['zoom'], 13, 0.5, 16, 6] as any
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'road-core-minor',
|
id: 'road-core-minor',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@@ -352,19 +378,37 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
'text-anchor': 'top',
|
'text-anchor': 'top',
|
||||||
'visibility': showPOIs ? 'visible' : 'none'
|
'visibility': showPOIs ? 'visible' : 'none'
|
||||||
},
|
},
|
||||||
paint: {
|
paint: {
|
||||||
'text-color': '#222',
|
'text-color': '#222',
|
||||||
'text-halo-color': 'rgba(255, 255, 255, 0.9)',
|
'text-halo-color': 'rgba(255, 255, 255, 0.9)',
|
||||||
'text-halo-width': 2
|
'text-halo-width': 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
center: [35.9106, 31.9539],
|
center: [35.9106, 31.9539],
|
||||||
zoom: 12,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
map.current.on('load', () => {
|
map.current = initialMap;
|
||||||
|
|
||||||
|
// Request User Location
|
||||||
|
if ('geolocation' in navigator) {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
if (initialMap) {
|
||||||
|
initialMap.flyTo({
|
||||||
|
center: [position.coords.longitude, position.coords.latitude],
|
||||||
|
zoom: 14,
|
||||||
|
essential: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => console.warn('Geolocation denied or failed', error),
|
||||||
|
{ timeout: 5000 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialMap.on('load', () => {
|
||||||
console.log("MapComponent: Map Loaded Successfully");
|
console.log("MapComponent: Map Loaded Successfully");
|
||||||
onMapLoad(map.current!);
|
onMapLoad(map.current!);
|
||||||
});
|
});
|
||||||
@@ -394,13 +438,13 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div ref={mapContainer} style={{ width: '100%', height: '100%', position: 'relative' }}>
|
<div ref={mapContainer} style={{ width: '100%', height: '100%', position: 'relative' }}>
|
||||||
{/* Intaleq Premium Branding Watermark */}
|
{/* Intaleq Premium Branding Watermark */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '10px',
|
bottom: '10px',
|
||||||
left: '10px',
|
left: '10px',
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: 'rgba(255, 255, 255, 0.85)',
|
background: 'rgba(255, 255, 255, 0.85)',
|
||||||
backdropFilter: 'blur(12px) saturate(180%)',
|
backdropFilter: 'blur(12px) saturate(180%)',
|
||||||
@@ -414,16 +458,16 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
|||||||
border: '1px solid rgba(255,255,255,0.35)'
|
border: '1px solid rgba(255,255,255,0.35)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/intaleq-logo.png"
|
src="/intaleq-logo.png"
|
||||||
alt="Intaleq"
|
alt="Intaleq"
|
||||||
style={{ height: '18px', filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.1))' }}
|
style={{ height: '18px', filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.1))' }}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
color: '#c0a048',
|
color: '#c0a048',
|
||||||
letterSpacing: '0.8px',
|
letterSpacing: '0.8px',
|
||||||
textTransform: 'uppercase'
|
textTransform: 'uppercase'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import 'maplibre-gl/dist/maplibre-gl.css';
|
|||||||
* Purpose: visually confirm whether a street/place exists before adding or approving it.
|
* Purpose: visually confirm whether a street/place exists before adding or approving it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TILES = (import.meta as any).env.VITE_TILES_URL || 'https://tiles.intaleqapp.com';
|
// Martin vector-tile host (serves planet_osm_*, approved_roads, …). This is a
|
||||||
|
// DIFFERENT host from where the app + its style.json are served, so it must be
|
||||||
|
// absolute — an empty default resolved to the app host, which serves no tiles.
|
||||||
|
const TILES = (import.meta as any).env.VITE_TILES_URL || 'http://188.68.36.205:3202';
|
||||||
|
|
||||||
type RefKey = 'google-sat' | 'esri-sat' | 'esri-streets' | 'osm';
|
type RefKey = 'google-sat' | 'esri-sat' | 'esri-streets' | 'osm';
|
||||||
const REFS: Record<RefKey, { label: string; tiles: string; attribution: string; maxzoom: number }> = {
|
const REFS: Record<RefKey, { label: string; tiles: string; attribution: string; maxzoom: number }> = {
|
||||||
@@ -48,14 +51,27 @@ const CompareView: React.FC = () => {
|
|||||||
const start: [number, number] = [35.91, 31.95];
|
const start: [number, number] = [35.91, 31.95];
|
||||||
const startZoom = 15;
|
const startZoom = 15;
|
||||||
|
|
||||||
const lMap = new maplibregl.Map({ container: leftDiv.current, style: TILES + '/style.json', center: start, zoom: startZoom, attributionControl: false });
|
// Base style is served by the APP host as a static file (its internal sources
|
||||||
|
// already point at the absolute Martin host), so load it relatively — never
|
||||||
|
// prefixed with the tile host, which does not serve /style.json.
|
||||||
|
const lMap = new maplibregl.Map({ container: leftDiv.current, style: '/style.json', center: start, zoom: startZoom, attributionControl: false });
|
||||||
const rMap = new maplibregl.Map({ container: rightDiv.current, style: rasterStyle(REFS['esri-sat']), center: start, zoom: startZoom, attributionControl: false });
|
const rMap = new maplibregl.Map({ container: rightDiv.current, style: rasterStyle(REFS['esri-sat']), center: start, zoom: startZoom, attributionControl: false });
|
||||||
lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
|
lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
|
||||||
rMap.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
|
rMap.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
|
||||||
|
|
||||||
|
if ('geolocation' in navigator) {
|
||||||
|
navigator.geolocation.getCurrentPosition((pos) => {
|
||||||
|
const coords: [number, number] = [pos.coords.longitude, pos.coords.latitude];
|
||||||
|
lMap.flyTo({ center: coords, zoom: 14, essential: true });
|
||||||
|
rMap.flyTo({ center: coords, zoom: 14, essential: true });
|
||||||
|
setCenter({ lng: coords[0], lat: coords[1], zoom: 14 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
lMap.on('load', () => {
|
lMap.on('load', () => {
|
||||||
lMap.addSource('approved', { type: 'vector', tiles: [`${TILES}/approved_roads/{z}/{x}/{y}`], minzoom: 8, maxzoom: 18 });
|
lMap.addSource('approved', { type: 'vector', tiles: [`${TILES}/approved_roads/{z}/{x}/{y}`], minzoom: 8, maxzoom: 18 });
|
||||||
lMap.addLayer({ id: 'approved', type: 'line', source: 'approved', 'source-layer': 'approved_roads', paint: { 'line-color': '#22c55e', 'line-width': ['interpolate', ['linear'], ['zoom'], 10, 1.5, 16, 8] } });
|
lMap.addLayer({ id: 'approved-casing', type: 'line', source: 'approved', 'source-layer': 'approved_roads', paint: { 'line-color': '#d4d4d4', 'line-width': ['interpolate', ['linear'], ['zoom'], 13, 1, 16, 8] } });
|
||||||
|
lMap.addLayer({ id: 'approved', type: 'line', source: 'approved', 'source-layer': 'approved_roads', paint: { 'line-color': '#ffffff', 'line-width': ['interpolate', ['linear'], ['zoom'], 13, 0.5, 16, 6] } });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep the two views locked together. The guard stops the move→jumpTo→move loop.
|
// Keep the two views locked together. The guard stops the move→jumpTo→move loop.
|
||||||
|
|||||||
@@ -59,15 +59,18 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
}, [drawing]);
|
}, [drawing]);
|
||||||
|
|
||||||
const redrawDrawing = () => {
|
const redrawDrawing = () => {
|
||||||
const lm = leftMapRef.current;
|
const lMap = leftMapRef.current; const rMap = rightMapRef.current;
|
||||||
const rm = rightMapRef.current;
|
if (!lMap || !rMap) return;
|
||||||
const pts = pointsRef.current;
|
const pts = pointsRef.current;
|
||||||
const feats: any[] = [];
|
const data = {
|
||||||
pts.forEach(p => feats.push({ type: 'Feature', geometry: { type: 'Point', coordinates: p } }));
|
type: 'FeatureCollection',
|
||||||
if (pts.length > 1) feats.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: pts } });
|
features: [
|
||||||
const col = { type: 'FeatureCollection', features: feats };
|
...(pts.length >= 2 ? [{ type: 'Feature' as const, geometry: { type: 'LineString' as const, coordinates: pts }, properties: {} }] : []),
|
||||||
if (lm && lm.isStyleLoaded()) (lm.getSource('draw') as maplibregl.GeoJSONSource)?.setData(col as any);
|
...pts.map(p => ({ type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: p }, properties: {} }))
|
||||||
if (rm && rm.isStyleLoaded()) (rm.getSource('draw') as maplibregl.GeoJSONSource)?.setData(col as any);
|
]
|
||||||
|
};
|
||||||
|
(lMap.getSource('draw') as maplibregl.GeoJSONSource)?.setData(data as any);
|
||||||
|
(rMap.getSource('draw') as maplibregl.GeoJSONSource)?.setData(data as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushMapData = () => {
|
const pushMapData = () => {
|
||||||
@@ -163,6 +166,15 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
|
lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
|
||||||
|
rMap.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
|
||||||
|
|
||||||
|
if ('geolocation' in navigator) {
|
||||||
|
navigator.geolocation.getCurrentPosition((pos) => {
|
||||||
|
const coords: [number, number] = [pos.coords.longitude, pos.coords.latitude];
|
||||||
|
lMap.flyTo({ center: coords, zoom: 14, essential: true });
|
||||||
|
rMap.flyTo({ center: coords, zoom: 14, essential: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Sync logic
|
// Sync logic
|
||||||
const link = (from: maplibregl.Map, to: maplibregl.Map) => {
|
const link = (from: maplibregl.Map, to: maplibregl.Map) => {
|
||||||
@@ -178,6 +190,14 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
lMap.addSource('cands', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
lMap.addSource('cands', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
||||||
lMap.addSource('closures', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
lMap.addSource('closures', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
||||||
lMap.addSource('hl', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
lMap.addSource('hl', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
||||||
|
|
||||||
|
// approved_roads is served by Martin on the tile host — NOT the app host. An
|
||||||
|
// empty default made this resolve to /approved_roads on the app host (404), so
|
||||||
|
// approved roads never drew. Point it at the real Martin host.
|
||||||
|
const TILES = (import.meta as any).env.VITE_TILES_URL || 'https://tiles.intaleqapp.com';
|
||||||
|
lMap.addSource('approved', { type: 'vector', tiles: [`${TILES}/approved_roads/{z}/{x}/{y}`], minzoom: 8, maxzoom: 18 });
|
||||||
|
lMap.addLayer({ id: 'approved-casing', type: 'line', source: 'approved', 'source-layer': 'approved_roads', paint: { 'line-color': '#d4d4d4', 'line-width': ['interpolate', ['linear'], ['zoom'], 13, 1, 16, 8] } });
|
||||||
|
lMap.addLayer({ id: 'approved', type: 'line', source: 'approved', 'source-layer': 'approved_roads', paint: { 'line-color': '#ffffff', 'line-width': ['interpolate', ['linear'], ['zoom'], 13, 0.5, 16, 6] } });
|
||||||
lMap.addSource('draw', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
lMap.addSource('draw', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } } as any);
|
||||||
|
|
||||||
lMap.addLayer({ id: 'cl', type: 'line', source: 'closures', paint: { 'line-color': '#ef4444', 'line-width': 4, 'line-dasharray': [3, 2] } });
|
lMap.addLayer({ id: 'cl', type: 'line', source: 'closures', paint: { 'line-color': '#ef4444', 'line-width': 4, 'line-dasharray': [3, 2] } });
|
||||||
@@ -208,18 +228,31 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
|
|
||||||
const onCandidateClick = (e: any) => {
|
const onCandidateClick = (e: any) => {
|
||||||
const p = e.features?.[0]?.properties as any; if (!p) return;
|
const p = e.features?.[0]?.properties as any; if (!p) return;
|
||||||
|
const l = e.features?.[0]?.layer?.id;
|
||||||
setSel(p.id);
|
setSel(p.id);
|
||||||
const isOvt = p.source === 'overture';
|
const isOvt = p.source === 'overture';
|
||||||
const title = isOvt ? '🗺️ Overture road (missing)' : '🛣️ Driver-traced road';
|
const isAppr = l === 'approved';
|
||||||
|
const title = isAppr ? '✅ Approved Road' : isOvt ? '🗺️ Overture road (missing)' : '🛣️ Driver-traced road';
|
||||||
const nameLine = p.name ? `<b>${p.name}</b><br/>` : '';
|
const nameLine = p.name ? `<b>${p.name}</b><br/>` : '';
|
||||||
const evidence = isOvt ? `${p.uniqueDriverCount ?? 0} trace pts corroborate` : `${p.uniqueDriverCount} drivers`;
|
const evidence = isOvt ? `${p.uniqueDriverCount ?? 0} trace pts corroborate` : `${p.uniqueDriverCount || 1} drivers`;
|
||||||
if (popupRef.current) popupRef.current.remove();
|
if (popupRef.current) popupRef.current.remove();
|
||||||
popupRef.current = new maplibregl.Popup({ offset: 12 }).setLngLat(e.lngLat)
|
|
||||||
.setHTML(`<div style="font:12px sans-serif;color:#1e293b;line-height:1.6">${nameLine}<b>${title}</b><br/>Conf: <b style="color:${cc(p.confidence)}">${Math.round(p.confidence * 100)}%</b><br/>${Math.round(p.lengthMeters)}m | ${evidence}</div>`)
|
const popupDiv = document.createElement('div');
|
||||||
.addTo(lMap);
|
popupDiv.style.font = '12px sans-serif';
|
||||||
|
popupDiv.style.color = '#1e293b';
|
||||||
|
popupDiv.style.lineHeight = '1.6';
|
||||||
|
popupDiv.innerHTML = `${nameLine}<b>${title}</b><br/>Conf: <b style="color:${cc(p.confidence)}">${Math.round(p.confidence * 100)}%</b><br/>${Math.round(p.lengthMeters || 0)}m | ${evidence}
|
||||||
|
<br/><button id="btn-del-${p.id}" style="margin-top:8px;background:#ef4444;color:white;border:none;border-radius:4px;padding:4px 8px;cursor:pointer;">🗑️ Delete Road</button>`;
|
||||||
|
|
||||||
|
popupDiv.querySelector(`#btn-del-${p.id}`)?.addEventListener('click', () => {
|
||||||
|
deleteRoad(p.id, isAppr ? 'approved' : 'candidate');
|
||||||
|
popupRef.current?.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
popupRef.current = new maplibregl.Popup({ offset: 12 }).setLngLat(e.lngLat).setDOMContent(popupDiv).addTo(lMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
['cp', 'cp-overture'].forEach(layer => {
|
['cp', 'cp-overture', 'approved'].forEach(layer => {
|
||||||
lMap.on('click', layer, onCandidateClick);
|
lMap.on('click', layer, onCandidateClick);
|
||||||
lMap.on('mouseenter', layer, () => { lMap.getCanvas().style.cursor = 'pointer'; });
|
lMap.on('mouseenter', layer, () => { lMap.getCanvas().style.cursor = 'pointer'; });
|
||||||
lMap.on('mouseleave', layer, () => { lMap.getCanvas().style.cursor = ''; });
|
lMap.on('mouseleave', layer, () => { lMap.getCanvas().style.cursor = ''; });
|
||||||
@@ -278,6 +311,25 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
setTimeout(() => setMsg(''), 5000);
|
setTimeout(() => setMsg(''), 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteRoad = async (id: string, type: 'approved' | 'candidate') => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}/map-refinement/roads/${type}/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'x-api-key': apiKey }
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Failed to delete');
|
||||||
|
setMsg(`🗑️ Deleted ${type} road successfully.`);
|
||||||
|
setSel(null);
|
||||||
|
(leftMapRef.current?.getSource('hl') as maplibregl.GeoJSONSource)?.setData({ type: 'FeatureCollection', features: [] });
|
||||||
|
load();
|
||||||
|
// Force MapLibre to drop cached vector tiles completely for approved roads
|
||||||
|
if (type === 'approved') {
|
||||||
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
|
}
|
||||||
|
} catch { setMsg('Delete failed.'); }
|
||||||
|
setTimeout(() => setMsg(''), 5000);
|
||||||
|
};
|
||||||
|
|
||||||
const runAI = async () => {
|
const runAI = async () => {
|
||||||
setMsg('🧠 Deep analysis started...');
|
setMsg('🧠 Deep analysis started...');
|
||||||
try {
|
try {
|
||||||
@@ -298,18 +350,18 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
setTimeout(() => setMsg(''), 6000);
|
setTimeout(() => setMsg(''), 6000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitDrawing = async () => {
|
const submitRoad = async () => {
|
||||||
if (pointsRef.current.length < 2) return setMsg('Draw at least 2 points!');
|
if (pointsRef.current.length < 2) return setMsg('Draw at least 2 points!');
|
||||||
|
|
||||||
const roadName = window.prompt('Enter a name for this new road:', 'Unnamed Road');
|
const roadName = window.prompt('Enter a name for this new road:', 'Unnamed Road');
|
||||||
if (roadName === null) return; // User cancelled
|
if (roadName === null) return;
|
||||||
|
const highway = window.prompt('Enter road category (primary, secondary, residential, unclassified, service):', 'unclassified');
|
||||||
|
if (highway === null) return;
|
||||||
try {
|
try {
|
||||||
const geojson = { type: 'LineString', coordinates: pointsRef.current };
|
const geojson = { type: 'LineString', coordinates: pointsRef.current };
|
||||||
const res = await fetch(`${API}/map-refinement/roads/candidates/manual`, {
|
const res = await fetch(`${API}/map-refinement/roads/candidates/manual`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||||
body: JSON.stringify({ geojson, name: roadName }),
|
body: JSON.stringify({ geojson, name: roadName, highway }),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error('Failed to submit road');
|
if (!res.ok) throw new Error('Failed to submit road');
|
||||||
setMsg('Road submitted for intelligence analysis!');
|
setMsg('Road submitted for intelligence analysis!');
|
||||||
@@ -400,7 +452,7 @@ const IntelligenceDashboard: React.FC = () => {
|
|||||||
<div style={{ background: '#1e3a8a30', border: '1px solid #3b82f6', padding: '10px', borderRadius: '8px', fontSize: '0.75rem', color: '#bfdbfe' }}>
|
<div style={{ background: '#1e3a8a30', border: '1px solid #3b82f6', padding: '10px', borderRadius: '8px', fontSize: '0.75rem', color: '#bfdbfe' }}>
|
||||||
Click on <b>either map</b> to trace the street.<br />
|
Click on <b>either map</b> to trace the street.<br />
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
||||||
<button onClick={submitDrawing} style={{ flex: 1, background: '#3b82f6', color: 'white', border: 'none', padding: '6px', borderRadius: '6px', cursor: 'pointer', fontWeight: 600 }}>Submit Road</button>
|
<button onClick={submitRoad} style={{ flex: 1, background: '#3b82f6', color: 'white', border: 'none', padding: '6px', borderRadius: '6px', cursor: 'pointer', fontWeight: 600 }}>Submit Road</button>
|
||||||
<button onClick={() => { pointsRef.current = []; redrawDrawing(); }} style={{ flex: 1, background: 'transparent', color: '#94a3b8', border: '1px solid #334155', padding: '6px', borderRadius: '6px', cursor: 'pointer', fontWeight: 600 }}>Clear</button>
|
<button onClick={() => { pointsRef.current = []; redrawDrawing(); }} style={{ flex: 1, background: 'transparent', color: '#94a3b8', border: '1px solid #334155', padding: '6px', borderRadius: '6px', cursor: 'pointer', fontWeight: 600 }}>Clear</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,6 +70,14 @@
|
|||||||
"https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"
|
"https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"
|
||||||
],
|
],
|
||||||
"maxzoom": 14
|
"maxzoom": 14
|
||||||
|
},
|
||||||
|
"approved_roads": {
|
||||||
|
"type": "vector",
|
||||||
|
"tiles": [
|
||||||
|
"http://188.68.36.205:3202/approved_roads/{z}/{x}/{y}"
|
||||||
|
],
|
||||||
|
"minzoom": 8,
|
||||||
|
"maxzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layers": [
|
"layers": [
|
||||||
@@ -691,6 +699,59 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-casing",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#D4D8DF",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
1.5,
|
||||||
|
16,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"line-opacity": 0.7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-core",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#FFFFFF",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
0.8,
|
||||||
|
16,
|
||||||
|
8
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "road-casing-tertiary",
|
"id": "road-casing-tertiary",
|
||||||
"type": "line",
|
"type": "line",
|
||||||
|
|||||||
@@ -70,6 +70,14 @@
|
|||||||
"https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"
|
"https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"
|
||||||
],
|
],
|
||||||
"maxzoom": 14
|
"maxzoom": 14
|
||||||
|
},
|
||||||
|
"approved_roads": {
|
||||||
|
"type": "vector",
|
||||||
|
"tiles": [
|
||||||
|
"http://188.68.36.205:3202/approved_roads/{z}/{x}/{y}"
|
||||||
|
],
|
||||||
|
"minzoom": 8,
|
||||||
|
"maxzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layers": [
|
"layers": [
|
||||||
@@ -691,6 +699,59 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-casing",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#D4D8DF",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
1.5,
|
||||||
|
16,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"line-opacity": 0.7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "approved-road-core",
|
||||||
|
"type": "line",
|
||||||
|
"source": "approved_roads",
|
||||||
|
"source-layer": "approved_roads",
|
||||||
|
"layout": {
|
||||||
|
"line-cap": "round",
|
||||||
|
"line-join": "round"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#FFFFFF",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
12,
|
||||||
|
0.8,
|
||||||
|
16,
|
||||||
|
8
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "road-casing-tertiary",
|
"id": "road-casing-tertiary",
|
||||||
"type": "line",
|
"type": "line",
|
||||||
|
|||||||
+4
-4
@@ -74,7 +74,7 @@
|
|||||||
"approved_roads": {
|
"approved_roads": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"tiles": [
|
"tiles": [
|
||||||
"https://tiles.intaleqapp.com/approved_roads/{z}/{x}/{y}"
|
"http://188.68.36.205:3202/approved_roads/{z}/{x}/{y}"
|
||||||
],
|
],
|
||||||
"minzoom": 8,
|
"minzoom": 8,
|
||||||
"maxzoom": 18
|
"maxzoom": 18
|
||||||
@@ -709,7 +709,7 @@
|
|||||||
"line-join": "round"
|
"line-join": "round"
|
||||||
},
|
},
|
||||||
"paint": {
|
"paint": {
|
||||||
"line-color": "#B9C2CE",
|
"line-color": "#D4D8DF",
|
||||||
"line-width": [
|
"line-width": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
[
|
[
|
||||||
@@ -719,11 +719,11 @@
|
|||||||
"zoom"
|
"zoom"
|
||||||
],
|
],
|
||||||
12,
|
12,
|
||||||
2.2,
|
1.5,
|
||||||
16,
|
16,
|
||||||
10
|
10
|
||||||
],
|
],
|
||||||
"line-opacity": 0.9
|
"line-opacity": 0.7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-4
@@ -74,7 +74,7 @@
|
|||||||
"approved_roads": {
|
"approved_roads": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"tiles": [
|
"tiles": [
|
||||||
"https://tiles.intaleqapp.com/approved_roads/{z}/{x}/{y}"
|
"http://188.68.36.205:3202/approved_roads/{z}/{x}/{y}"
|
||||||
],
|
],
|
||||||
"minzoom": 8,
|
"minzoom": 8,
|
||||||
"maxzoom": 18
|
"maxzoom": 18
|
||||||
@@ -709,7 +709,7 @@
|
|||||||
"line-join": "round"
|
"line-join": "round"
|
||||||
},
|
},
|
||||||
"paint": {
|
"paint": {
|
||||||
"line-color": "#B9C2CE",
|
"line-color": "#D4D8DF",
|
||||||
"line-width": [
|
"line-width": [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
[
|
[
|
||||||
@@ -719,11 +719,11 @@
|
|||||||
"zoom"
|
"zoom"
|
||||||
],
|
],
|
||||||
12,
|
12,
|
||||||
2.2,
|
1.5,
|
||||||
16,
|
16,
|
||||||
10
|
10
|
||||||
],
|
],
|
||||||
"line-opacity": 0.9
|
"line-opacity": 0.7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ if [ $? -eq 0 ]; then
|
|||||||
echo "------------------------------------------------"
|
echo "------------------------------------------------"
|
||||||
echo "Now run the following on your SERVER terminal:"
|
echo "Now run the following on your SERVER terminal:"
|
||||||
echo "cd $REMOTE_PATH"
|
echo "cd $REMOTE_PATH"
|
||||||
echo "docker compose up -d --build api dashboard"
|
echo "docker compose up -d --build api dashboard web"
|
||||||
echo "Dashboard will be live at: http://$SERVER_IP:3204"
|
echo "Dashboard will be live at: http://$SERVER_IP:3204"
|
||||||
echo "------------------------------------------------"
|
echo "------------------------------------------------"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user