Spaces:
Running
Running
Update src/modules/map_dashboard.py
Browse files- src/modules/map_dashboard.py +148 -112
src/modules/map_dashboard.py
CHANGED
|
@@ -120,117 +120,153 @@ def show_map_dashboard(client, sheet_name):
|
|
| 120 |
st.caption(f"Rev: {row['Revenus']} XOF")
|
| 121 |
|
| 122 |
with col_map:
|
| 123 |
-
#
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
'elevation': [500]
|
| 132 |
-
})
|
| 133 |
-
|
| 134 |
-
# Configuration de la vue 3D
|
| 135 |
-
view_state = pdk.ViewState(
|
| 136 |
-
latitude=48.913418,
|
| 137 |
-
longitude=2.396667,
|
| 138 |
-
zoom=13,
|
| 139 |
-
pitch=60, # Inclinaison 3D
|
| 140 |
-
bearing=0
|
| 141 |
-
)
|
| 142 |
-
|
| 143 |
-
layers = []
|
| 144 |
-
|
| 145 |
-
# Layer 1: Colonnes 3D pour les clients (losanges bleus)
|
| 146 |
-
layers.append(
|
| 147 |
-
pdk.Layer(
|
| 148 |
-
'ColumnLayer',
|
| 149 |
-
data=clients_layer_data,
|
| 150 |
-
get_position='[lon, lat]',
|
| 151 |
-
get_elevation=200,
|
| 152 |
-
elevation_scale=1,
|
| 153 |
-
radius=30,
|
| 154 |
-
get_fill_color=[37, 193, 247, 200],
|
| 155 |
-
pickable=True,
|
| 156 |
-
auto_highlight=True,
|
| 157 |
-
)
|
| 158 |
-
)
|
| 159 |
-
|
| 160 |
-
# Layer 2: HQ Pyramide Rouge (style Palantir)
|
| 161 |
-
layers.append(
|
| 162 |
-
pdk.Layer(
|
| 163 |
-
'ColumnLayer',
|
| 164 |
-
data=hq_data,
|
| 165 |
-
get_position='[lon, lat]',
|
| 166 |
-
get_elevation='elevation',
|
| 167 |
-
elevation_scale=1,
|
| 168 |
-
radius=50,
|
| 169 |
-
get_fill_color=[255, 50, 50, 255],
|
| 170 |
-
pickable=True,
|
| 171 |
-
)
|
| 172 |
-
)
|
| 173 |
-
|
| 174 |
-
# Layer 3: Lignes de connexion (si activées)
|
| 175 |
-
if show_dist:
|
| 176 |
-
arc_data = []
|
| 177 |
-
for _, row in df_map.iterrows():
|
| 178 |
-
arc_data.append({
|
| 179 |
-
'source_lon': 2.396667,
|
| 180 |
-
'source_lat': 48.913418,
|
| 181 |
-
'target_lon': row['lon'],
|
| 182 |
-
'target_lat': row['lat']
|
| 183 |
-
})
|
| 184 |
-
|
| 185 |
-
arc_df = pd.DataFrame(arc_data)
|
| 186 |
-
|
| 187 |
-
layers.append(
|
| 188 |
-
pdk.Layer(
|
| 189 |
-
'ArcLayer',
|
| 190 |
-
data=arc_df,
|
| 191 |
-
get_source_position='[source_lon, source_lat]',
|
| 192 |
-
get_target_position='[target_lon, target_lat]',
|
| 193 |
-
get_source_color=[255, 215, 0, 180],
|
| 194 |
-
get_target_color=[37, 193, 247, 150],
|
| 195 |
-
get_width=2,
|
| 196 |
-
pickable=True,
|
| 197 |
-
)
|
| 198 |
-
)
|
| 199 |
-
|
| 200 |
-
# Layer 4: Zone de densité (si activée)
|
| 201 |
-
if show_cluster:
|
| 202 |
-
avg_lat = df_map['lat'].mean()
|
| 203 |
-
avg_lon = df_map['lon'].mean()
|
| 204 |
-
|
| 205 |
-
circle_data = pd.DataFrame({
|
| 206 |
-
'lon': [avg_lon],
|
| 207 |
-
'lat': [avg_lat],
|
| 208 |
-
'radius': [2000]
|
| 209 |
})
|
| 210 |
-
|
| 211 |
-
layers.append(
|
| 212 |
-
pdk.Layer(
|
| 213 |
-
'ScatterplotLayer',
|
| 214 |
-
data=circle_data,
|
| 215 |
-
get_position='[lon, lat]',
|
| 216 |
-
get_radius='radius',
|
| 217 |
-
get_fill_color=[84, 189, 75, 80],
|
| 218 |
-
pickable=False,
|
| 219 |
-
)
|
| 220 |
-
)
|
| 221 |
|
| 222 |
-
#
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
st.caption(f"Rev: {row['Revenus']} XOF")
|
| 121 |
|
| 122 |
with col_map:
|
| 123 |
+
# Préparer les données des clients
|
| 124 |
+
clients_markers = []
|
| 125 |
+
for _, row in df_map.iterrows():
|
| 126 |
+
clients_markers.append({
|
| 127 |
+
'lon': row['lon'],
|
| 128 |
+
'lat': row['lat'],
|
| 129 |
+
'id': row['ID'],
|
| 130 |
+
'adresse': row['Adresse']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
# Créer le HTML avec Mapbox GL
|
| 134 |
+
mapbox_html = f"""
|
| 135 |
+
<!DOCTYPE html>
|
| 136 |
+
<html>
|
| 137 |
+
<head>
|
| 138 |
+
<meta charset="utf-8">
|
| 139 |
+
<script src='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js'></script>
|
| 140 |
+
<link href='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css' rel='stylesheet' />
|
| 141 |
+
<style>
|
| 142 |
+
body {{ margin: 0; padding: 0; }}
|
| 143 |
+
#map {{ position: absolute; top: 0; bottom: 0; width: 100%; }}
|
| 144 |
+
</style>
|
| 145 |
+
</head>
|
| 146 |
+
<body>
|
| 147 |
+
<div id="map"></div>
|
| 148 |
+
<script>
|
| 149 |
+
mapboxgl.accessToken = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; // Token public Mapbox
|
| 150 |
+
|
| 151 |
+
const map = new mapboxgl.Map({{
|
| 152 |
+
container: 'map',
|
| 153 |
+
style: 'mapbox://styles/mapbox/satellite-streets-v12', // Vue satellite
|
| 154 |
+
center: [2.396667, 48.913418],
|
| 155 |
+
zoom: 15,
|
| 156 |
+
pitch: 60, // Vue 3D inclinée
|
| 157 |
+
bearing: 0,
|
| 158 |
+
antialias: true
|
| 159 |
+
}});
|
| 160 |
+
|
| 161 |
+
map.on('load', () => {{
|
| 162 |
+
// Activer les bâtiments 3D
|
| 163 |
+
map.addLayer({{
|
| 164 |
+
'id': '3d-buildings',
|
| 165 |
+
'source': 'composite',
|
| 166 |
+
'source-layer': 'building',
|
| 167 |
+
'filter': ['==', 'extrude', 'true'],
|
| 168 |
+
'type': 'fill-extrusion',
|
| 169 |
+
'minzoom': 14,
|
| 170 |
+
'paint': {{
|
| 171 |
+
'fill-extrusion-color': '#aaa',
|
| 172 |
+
'fill-extrusion-height': ['get', 'height'],
|
| 173 |
+
'fill-extrusion-base': ['get', 'min_height'],
|
| 174 |
+
'fill-extrusion-opacity': 0.8
|
| 175 |
+
}}
|
| 176 |
+
}});
|
| 177 |
+
|
| 178 |
+
// Marqueur HQ (Pyramide Rouge)
|
| 179 |
+
const hqEl = document.createElement('div');
|
| 180 |
+
hqEl.style.width = '30px';
|
| 181 |
+
hqEl.style.height = '30px';
|
| 182 |
+
hqEl.style.backgroundImage = 'linear-gradient(135deg, #ff3232 0%, #aa0000 100%)';
|
| 183 |
+
hqEl.style.transform = 'rotate(45deg)';
|
| 184 |
+
hqEl.style.border = '2px solid #fff';
|
| 185 |
+
hqEl.style.boxShadow = '0 0 20px rgba(255, 50, 50, 0.8)';
|
| 186 |
+
|
| 187 |
+
new mapboxgl.Marker({{element: hqEl}})
|
| 188 |
+
.setLngLat([2.396667, 48.913418])
|
| 189 |
+
.setPopup(new mapboxgl.Popup().setHTML('<strong>HQ - VORTEX</strong>'))
|
| 190 |
+
.addTo(map);
|
| 191 |
+
|
| 192 |
+
// Marqueurs Clients (Losanges Bleus)
|
| 193 |
+
const clients = {clients_markers};
|
| 194 |
+
clients.forEach(client => {{
|
| 195 |
+
const el = document.createElement('div');
|
| 196 |
+
el.style.width = '20px';
|
| 197 |
+
el.style.height = '20px';
|
| 198 |
+
el.style.background = 'linear-gradient(135deg, #25C1F7 0%, #0080ff 100%)';
|
| 199 |
+
el.style.transform = 'rotate(45deg)';
|
| 200 |
+
el.style.border = '1px solid #fff';
|
| 201 |
+
el.style.boxShadow = '0 0 15px rgba(37, 193, 247, 0.6)';
|
| 202 |
+
el.style.cursor = 'pointer';
|
| 203 |
+
|
| 204 |
+
new mapboxgl.Marker({{element: el}})
|
| 205 |
+
.setLngLat([client.lon, client.lat])
|
| 206 |
+
.setPopup(new mapboxgl.Popup().setHTML(`<strong>${{client.id}}</strong><br>${{client.adresse}}`))
|
| 207 |
+
.addTo(map);
|
| 208 |
+
|
| 209 |
+
// Lignes de connexion (si activé)
|
| 210 |
+
{'if (true) {' if show_dist else 'if (false) {'}
|
| 211 |
+
map.addLayer({{
|
| 212 |
+
'id': 'line-' + client.id,
|
| 213 |
+
'type': 'line',
|
| 214 |
+
'source': {{
|
| 215 |
+
'type': 'geojson',
|
| 216 |
+
'data': {{
|
| 217 |
+
'type': 'Feature',
|
| 218 |
+
'geometry': {{
|
| 219 |
+
'type': 'LineString',
|
| 220 |
+
'coordinates': [
|
| 221 |
+
[2.396667, 48.913418],
|
| 222 |
+
[client.lon, client.lat]
|
| 223 |
+
]
|
| 224 |
+
}}
|
| 225 |
+
}}
|
| 226 |
+
}},
|
| 227 |
+
'paint': {{
|
| 228 |
+
'line-color': '#FFD700',
|
| 229 |
+
'line-width': 2,
|
| 230 |
+
'line-opacity': 0.6
|
| 231 |
+
}}
|
| 232 |
+
}});
|
| 233 |
+
}}
|
| 234 |
+
}});
|
| 235 |
+
|
| 236 |
+
// Zone de cluster (si activé)
|
| 237 |
+
{'if (true) {' if show_cluster else 'if (false) {'}
|
| 238 |
+
map.addLayer({{
|
| 239 |
+
'id': 'cluster-zone',
|
| 240 |
+
'type': 'circle',
|
| 241 |
+
'source': {{
|
| 242 |
+
'type': 'geojson',
|
| 243 |
+
'data': {{
|
| 244 |
+
'type': 'Feature',
|
| 245 |
+
'geometry': {{
|
| 246 |
+
'type': 'Point',
|
| 247 |
+
'coordinates': [{df_map['lon'].mean()}, {df_map['lat'].mean()}]
|
| 248 |
+
}}
|
| 249 |
+
}}
|
| 250 |
+
}},
|
| 251 |
+
'paint': {{
|
| 252 |
+
'circle-radius': {{
|
| 253 |
+
'stops': [[0, 0], [20, 300]]
|
| 254 |
+
}},
|
| 255 |
+
'circle-color': '#54BD4B',
|
| 256 |
+
'circle-opacity': 0.3,
|
| 257 |
+
'circle-stroke-width': 2,
|
| 258 |
+
'circle-stroke-color': '#54BD4B'
|
| 259 |
+
}}
|
| 260 |
+
}});
|
| 261 |
+
}}
|
| 262 |
+
}});
|
| 263 |
+
|
| 264 |
+
// Contrôles de navigation
|
| 265 |
+
map.addControl(new mapboxgl.NavigationControl());
|
| 266 |
+
</script>
|
| 267 |
+
</body>
|
| 268 |
+
</html>
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
# Afficher la carte
|
| 272 |
+
st.components.v1.html(mapbox_html, height=600)
|