template html & css

This commit is contained in:
ph
2025-11-26 22:34:43 +01:00
commit ea21779b05
20 changed files with 3208 additions and 0 deletions

221
svg.html Normal file
View File

@@ -0,0 +1,221 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>SVG from LINESTRINGs</title>
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; padding: 12px; }
#mapWrap { width: 100%; height: 80vh; border: 1px solid #ddd; background: #f8f9fb; }
svg { width: 100%; height: 100%; display: block; }
.line-sens-1 { stroke: #1f77b4; stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.line-sens-2 { stroke: #ff7f0e; stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.hovered { stroke-width: 5; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2)); }
.vertex { r: 2.2; fill: rgba(0,0,0,0.5); pointer-events: none; }
.tooltip {
position: absolute;
pointer-events: none;
background: rgba(0,0,0,0.75);
color: white;
padding: 4px 6px;
font-size: 12px;
border-radius: 4px;
transform: translate(8px, 8px);
display: none;
z-index: 10;
white-space: nowrap;
}
</style>
</head>
<body>
<h1>Visualisation SVG des LINESTRING</h1>
<div id="mapWrap"></div>
<div id="tooltip" class="tooltip"></div>
<script>
/* --- données fournies par l'utilisateur --- */
const data = [
{
"fk_route_uuid": "8d004027-3279-4eb8-8de4-7f40ef542111",
"sens": 1,
"st_astext": "LINESTRING(240786.25000013656 79036.06584277935,240789.6701001395 79034.92654277757,240795.80500013664 79032.88284277637,240816.54300013464 79021.48284277692,240848.48400013373 79003.72684277594,240862.6130001347 78996.93084277771,240869.41630013572 78995.22494277824,240875.52820013772 78995.85994277615,240878.9446763978 78999.20556702279,240883.3350001378 79003.50484277774,240885.7940001368 79005.9478427777,240890.99600013572 79019.1748427758,240893.19700013375 79022.76474277768,240895.1890001397 79026.01384277735)"
},
{
"fk_route_uuid": "8d004027-3279-4eb8-8de4-7f40ef542111",
"sens": 2,
"st_astext": "LINESTRING(240876.93050013762 79027.10734277777,240880.75500013976 79023.49284277484,240883.52900013572 79017.29684277717,240884.25950013375 79012.2112427773,240884.10070013476 79007.92494277749,240883.3350001378 79003.50484277774,240878.94468326174 78999.20557374507,240875.52820013772 78995.85994277615,240869.41630013572 78995.22494277824,240862.6130001347 78996.93084277771,240848.48400013373 79003.72684277594,240816.54300013464 79021.48284277692,240795.80500013664 79032.88284277637,240789.6701001395 79034.92654277757)"
},
{
"fk_route_uuid": "e8af74b2-62bb-4ed9-841a-d72f01a471bf",
"sens": 1,
"st_astext": "LINESTRING(240788.70500013442 79077.95804277528,240726.47800013627 79107.39084277768,240719.76930013526 79110.84164277464,240718.30300013922 79111.59584277496,240713.81800014016 79116.28184277564,240711.32100013818 79123.2998427758,240711.94600013812 79131.3228427777,240710.94900013716 79134.09984277561,240718.46600013407 79143.87984277774,240728.20900013513 79148.93084277678,240737.10500013322 79148.99684277643,240749.14700013614 79143.51984277926,240772.7741277222 79129.7665901715,240784.77000013928 79122.7838427769,240811.7210001374 79105.72584277857)"
},
{
"fk_route_uuid": "e8af74b2-62bb-4ed9-841a-d72f01a471bf",
"sens": 2,
"st_astext": "LINESTRING(240811.7210001374 79105.72584277857,240784.77000013928 79122.7838427769,240772.77412333922 79129.76659272239,240749.14700013614 79143.51984277926,240737.10500013322 79148.99684277643,240728.20900013513 79148.93084277678,240718.46600013407 79143.87984277774,240710.94900013716 79134.09984277561,240708.14400013717 79132.68284277525,240702.59600013716 79122.08384277672,240697.10920013717 79117.31394277606)"
}
];
/* --- utilitaires --- */
/** Parse un WKT LINESTRING et renvoie un array de [x,y] */
function parseLinestring(wkt) {
// wkt attendu: "LINESTRING(x1 y1,x2 y2,...)"
const inner = wkt.replace(/^\s*LINESTRING\s*\(|\)\s*$/gi, '');
const parts = inner.split(',');
return parts.map(p => {
const [xs, ys] = p.trim().split(/\s+/);
return [parseFloat(xs), parseFloat(ys)];
});
}
/** calcule bbox pour toutes les géométries */
function computeBBox(allCoords) {
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
allCoords.forEach(coords => {
coords.forEach(([x,y]) => {
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
});
});
return { minX, minY, maxX, maxY };
}
/* --- construction du SVG --- */
(function render() {
const parsed = data.map(item => {
return {
fk_route_uuid: item.fk_route_uuid,
sens: item.sens,
coords: parseLinestring(item.st_astext)
};
});
const allCoords = parsed.map(p => p.coords);
const bbox = computeBBox(allCoords);
// padding en unités réelles (10% de la plus grande dimension)
const padFactor = 0.08;
const widthReal = bbox.maxX - bbox.minX || 1;
const heightReal = bbox.maxY - bbox.minY || 1;
const padX = widthReal * padFactor;
const padY = heightReal * padFactor;
const viewMinX = bbox.minX - padX;
const viewMinY = bbox.minY - padY;
const viewMaxX = bbox.maxX + padX;
const viewMaxY = bbox.maxY + padY;
const viewWidth = viewMaxX - viewMinX;
const viewHeight = viewMaxY - viewMinY;
// on veut que Y augmente vers le bas en SVG => on va inverser Y en transformant
// la vue en utilisant viewBox et en appliquant une transformation sur un <g>
const mapWrap = document.getElementById('mapWrap');
const svgNS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(svgNS, 'svg');
// viewBox: minX minY width height — on met minX, minY = viewMinX, viewMinY, mais
// pour inverser Y on va créer un group <g transform="scale(1,-1) translate(0, -H)">
svg.setAttribute('viewBox', `${viewMinX} ${viewMinY} ${viewWidth} ${viewHeight}`);
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
// container group : on doit inverser Y axis because coordinates are in projected system
// Approach: apply transform to flip Y around center. Simpler: we apply transform to map coordinates to SVG coords when building path.
// Here we'll use transform on group: scale(1,-1) and translate(0, - (viewMinY + viewMaxY) )
const g = document.createElementNS(svgNS, 'g');
// translate to flip around the middle of viewbox
const translateY = -(viewMinY + viewMaxY);
g.setAttribute('transform', `scale(1,-1) translate(0, ${translateY})`);
// for each parsed geometry create a path
parsed.forEach((item, idx) => {
const d = item.coords.map((pt, i) => {
const [x, y] = pt;
return (i === 0 ? 'M ' : 'L ') + x + ' ' + y;
}).join(' ');
const path = document.createElementNS(svgNS, 'path');
const cls = item.sens === 1 ? 'line-sens-1' : 'line-sens-2';
path.setAttribute('d', d);
path.setAttribute('class', cls);
path.setAttribute('data-index', idx);
path.setAttribute('data-uuid', item.fk_route_uuid);
path.setAttribute('data-sens', item.sens);
// cursor crosshair on hover:
path.style.cursor = 'crosshair';
// events
path.addEventListener('mouseenter', (e) => {
path.classList.add('hovered');
showTooltip(e, `route: ${item.fk_route_uuid}<br>sens: ${item.sens}`);
});
path.addEventListener('mousemove', (e) => {
moveTooltip(e);
});
path.addEventListener('mouseleave', () => {
path.classList.remove('hovered');
hideTooltip();
});
path.addEventListener('dblclick', (e) => {
e.stopPropagation();
// action au double-clic : highlight et log — personnalise ici
console.log('Double-clic sur', {
uuid: item.fk_route_uuid,
sens: item.sens,
index: idx
});
// effet visuel bref
path.classList.add('hovered');
setTimeout(() => path.classList.remove('hovered'), 700);
// si tu veux centrer / zoomer sur la géométrie, tu peux ajouter ici
});
g.appendChild(path);
// optionally draw small vertices (disable pointer events to avoid fuss)
item.coords.forEach(([x,y]) => {
const c = document.createElementNS(svgNS, 'circle');
c.setAttribute('cx', x);
c.setAttribute('cy', y);
c.setAttribute('r', 0.0); // mettre petit rayon si tu veux les voir, sinon 0
c.setAttribute('class', 'vertex');
g.appendChild(c);
});
});
svg.appendChild(g);
mapWrap.appendChild(svg);
/* Tooltip utilities */
const tooltip = document.getElementById('tooltip');
function showTooltip(evt, html) {
tooltip.innerHTML = html;
tooltip.style.display = 'block';
moveTooltip(evt);
}
function moveTooltip(evt) {
// placer le tooltip à côté du pointeur (page coords)
const x = evt.clientX;
const y = evt.clientY;
tooltip.style.left = (x + 10) + 'px';
tooltip.style.top = (y + 10) + 'px';
}
function hideTooltip() {
tooltip.style.display = 'none';
}
// clic global (ex: décocher highlight / action)
svg.addEventListener('click', () => {
// exemple : retirer highlight de toutes les lignes
svg.querySelectorAll('path.hovered').forEach(p => p.classList.remove('hovered'));
});
})();
</script>
</body>
</html>