Enable moving own features on MapLibre and switch to raster tiles.
CI / test (push) Successful in 4s
CI / test (push) Successful in 4s
Add feature geometry PATCH API support and update MapLibre demo to use OSM raster tiles, load all public/owned features, and let logged-in users drag their own feature markers to persist new positions. Made-with: Cursor
This commit is contained in:
@@ -99,7 +99,7 @@
|
||||
<div class="panel">
|
||||
<h1>MapLibre GL + Three.js Demo</h1>
|
||||
<div class="muted">
|
||||
Vector tiles + 3D object placement. Click map to place, upload GLB/GLTF, store and share.
|
||||
Raster tiles + 3D object placement. Click map to place, upload GLB/GLTF, store and share.
|
||||
</div>
|
||||
|
||||
<h2>Connection</h2>
|
||||
|
||||
+73
-1
@@ -36,6 +36,8 @@ let threeScene;
|
||||
let threeRenderer;
|
||||
let threeCamera;
|
||||
const featureMeshes = new Map();
|
||||
const ownFeatureMarkers = new Map();
|
||||
const ownFeatureCoords = new Map();
|
||||
|
||||
function setStatus(message) {
|
||||
statusEl.textContent = message;
|
||||
@@ -71,6 +73,27 @@ function setClientBase(baseUrl) {
|
||||
setStatus(`API base updated: ${normalized}`);
|
||||
}
|
||||
|
||||
function buildRasterStyle() {
|
||||
return {
|
||||
version: 8,
|
||||
sources: {
|
||||
"osm-raster": {
|
||||
type: "raster",
|
||||
tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"],
|
||||
tileSize: 256,
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: "osm-raster-layer",
|
||||
type: "raster",
|
||||
source: "osm-raster",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function buildMapShareLink(feature, asset) {
|
||||
const coords = feature?.geometry?.coordinates;
|
||||
if (!Array.isArray(coords) || coords.length < 2) {
|
||||
@@ -156,6 +179,14 @@ function clearFeatureMeshes() {
|
||||
featureMeshes.clear();
|
||||
}
|
||||
|
||||
function clearOwnFeatureMarkers() {
|
||||
for (const marker of ownFeatureMarkers.values()) {
|
||||
marker.remove();
|
||||
}
|
||||
ownFeatureMarkers.clear();
|
||||
ownFeatureCoords.clear();
|
||||
}
|
||||
|
||||
function addObjectMesh(featureId, lng, lat, isPublic, kind) {
|
||||
const merc = maplibregl.MercatorCoordinate.fromLngLat({ lng, lat }, 0);
|
||||
const meters = merc.meterInMercatorCoordinateUnits();
|
||||
@@ -173,6 +204,40 @@ function addObjectMesh(featureId, lng, lat, isPublic, kind) {
|
||||
featureMeshes.set(featureId, mesh);
|
||||
}
|
||||
|
||||
async function moveOwnFeature(featureID, lng, lat) {
|
||||
const existing = ownFeatureCoords.get(featureID);
|
||||
const alt = Array.isArray(existing) && existing.length >= 3 ? existing[2] : 0;
|
||||
const res = await fetch(`${currentApiBase()}/v1/features/${featureID}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
geometry: { type: "Point", coordinates: [lng, lat, alt] },
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const maybeJson = await res.json().catch(() => ({}));
|
||||
throw new Error(maybeJson.error || `Failed to move feature (${res.status})`);
|
||||
}
|
||||
}
|
||||
|
||||
function addOwnFeatureMarker(featureID, lng, lat) {
|
||||
const marker = new maplibregl.Marker({ color: "#2563eb", draggable: true }).setLngLat({ lng, lat }).addTo(map);
|
||||
marker.on("dragend", async () => {
|
||||
const p = marker.getLngLat();
|
||||
try {
|
||||
await moveOwnFeature(featureID, p.lng, p.lat);
|
||||
await refreshFeatures();
|
||||
setStatus(`Feature ${featureID} moved.`);
|
||||
} catch (error) {
|
||||
setStatus(error.message);
|
||||
}
|
||||
});
|
||||
ownFeatureMarkers.set(featureID, marker);
|
||||
}
|
||||
|
||||
async function ensureKeys() {
|
||||
keys = await client.ensureKeysInStorage();
|
||||
publicKeyPreviewEl.textContent = `Public key: ${keys.publicKey.slice(0, 24)}...`;
|
||||
@@ -259,6 +324,7 @@ async function refreshFeatures() {
|
||||
const publicData = await publicResp.json();
|
||||
const byID = new Map((publicData.features || []).map((feature) => [feature.id, feature]));
|
||||
|
||||
const ownFeatureIDs = new Set();
|
||||
if (accessToken) {
|
||||
const { collections } = await client.listCollections();
|
||||
if (!collectionId && collections.length > 0) {
|
||||
@@ -273,21 +339,27 @@ async function refreshFeatures() {
|
||||
);
|
||||
for (const feature of ownFeatureSets.flat()) {
|
||||
byID.set(feature.id, feature);
|
||||
ownFeatureIDs.add(feature.id);
|
||||
}
|
||||
}
|
||||
|
||||
const features = Array.from(byID.values());
|
||||
clearFeatureMeshes();
|
||||
clearOwnFeatureMarkers();
|
||||
for (const feature of features) {
|
||||
const coords = feature.geometry?.coordinates;
|
||||
if (!coords || coords.length < 2) continue;
|
||||
const lng = coords[0];
|
||||
const lat = coords[1];
|
||||
ownFeatureCoords.set(feature.id, coords);
|
||||
const assets = Array.isArray(feature.properties?.assets) ? feature.properties.assets : [];
|
||||
const first = assets[0];
|
||||
if (first) {
|
||||
addObjectMesh(feature.id, lng, lat, first.isPublic, first.kind);
|
||||
}
|
||||
if (ownFeatureIDs.has(feature.id) && accessToken) {
|
||||
addOwnFeatureMarker(feature.id, lng, lat);
|
||||
}
|
||||
}
|
||||
renderAssets(features);
|
||||
}
|
||||
@@ -385,7 +457,7 @@ document.getElementById("uploadAsset").onclick = async () => {
|
||||
|
||||
map = new maplibregl.Map({
|
||||
container: "map",
|
||||
style: "./osm-liberty-gl-style/style.json",
|
||||
style: buildRasterStyle(),
|
||||
center: [-16.2518, 28.4636],
|
||||
zoom: 12,
|
||||
pitch: 55,
|
||||
|
||||
Reference in New Issue
Block a user