Created
April 17, 2025 13:58
-
-
Save do-me/3397b78c40b2e0e751ecfc17b6254f27 to your computer and use it in GitHub Desktop.
maplibre custom rectangle drawing logic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>MapLibre Draw Rectangle</title> | |
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" /> | |
<!-- MapLibre GL JS --> | |
<script src='https://unpkg.com/[email protected]/dist/maplibre-gl.js'></script> | |
<link href='https://unpkg.com/[email protected]/dist/maplibre-gl.css' rel='stylesheet' /> | |
<style> | |
body { margin: 0; padding: 0; } | |
#map { position: absolute; top: 0; bottom: 0; width: 100%; } | |
.info { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
background: rgba(255, 255, 255, 0.8); | |
padding: 10px; | |
border-radius: 5px; | |
z-index: 1; | |
font-family: sans-serif; | |
} | |
#deleteButton { | |
margin-top: 5px; | |
padding: 5px 10px; | |
cursor: pointer; | |
} | |
#deleteButton:disabled { | |
cursor: not-allowed; | |
opacity: 0.6; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="map"></div> | |
<div class="info"> | |
<div>Click and drag to draw a rectangle.</div> | |
<button id="deleteButton" disabled>Delete Rectangle</button> | |
</div> | |
<script> | |
const geojsonSourceId = 'drawn-rectangle-source'; | |
const rectangleLayerId = 'drawn-rectangle-layer'; | |
const rectangleOutlineLayerId = 'drawn-rectangle-outline-layer'; | |
// --- State Variables --- | |
let map; | |
let isDrawing = false; | |
let startPoint = null; | |
let currentRectangleFeatureId = null; // Store the ID of the drawn feature | |
const deleteButton = document.getElementById('deleteButton'); | |
// --- Map Initialization --- | |
map = new maplibregl.Map({ | |
container: 'map', // container id | |
style: 'https://tiles.openfreemap.org/styles/liberty', | |
center: [-74.0060, 40.7128], // Initial center point [lng, lat] (New York) | |
zoom: 12 // Initial zoom level | |
}); | |
map.on('load', () => { | |
console.log('Map loaded.'); | |
// 1. Add the GeoJSON source (initially empty) | |
map.addSource(geojsonSourceId, { | |
type: 'geojson', | |
data: { | |
type: 'FeatureCollection', | |
features: [] // Start with no features | |
} | |
}); | |
// 2. Add the fill layer for the rectangle | |
map.addLayer({ | |
id: rectangleLayerId, | |
type: 'fill', | |
source: geojsonSourceId, | |
paint: { | |
'fill-color': '#007cbf', // Blueish color | |
'fill-opacity': 0.5 | |
} | |
}); | |
// 3. Add the outline layer for the rectangle | |
map.addLayer({ | |
id: rectangleOutlineLayerId, | |
type: 'line', | |
source: geojsonSourceId, | |
paint: { | |
'line-color': '#000000', | |
'line-width': 2, | |
'line-dasharray': [2, 2] // Dashed line while drawing, solid when finished | |
} | |
}); | |
// --- Drawing Event Listeners --- | |
map.on('mousedown', handleMouseDown); | |
map.on('mousemove', handleMouseMove); | |
map.on('mouseup', handleMouseUp); | |
// Prevent context menu on right-click drag (optional) | |
map.getCanvas().addEventListener('contextmenu', (e) => e.preventDefault()); | |
}); | |
// --- Event Handlers --- | |
function handleMouseDown(e) { | |
// Prevent drawing if a rectangle already exists | |
if (currentRectangleFeatureId) { | |
console.log("A rectangle already exists. Delete it first."); | |
return; | |
} | |
// Prevent drawing if the click is not a primary button click (left-click) | |
if (e.originalEvent.button !== 0) { | |
return; | |
} | |
isDrawing = true; | |
startPoint = e.lngLat; // Capture starting coordinates | |
console.log('Drawing started at:', startPoint); | |
// Disable map panning while drawing | |
map.dragPan.disable(); | |
// Change cursor to indicate drawing | |
map.getCanvas().style.cursor = 'crosshair'; | |
// Prevent default map drag behavior | |
e.preventDefault(); | |
} | |
function handleMouseMove(e) { | |
if (!isDrawing) return; // Only run if currently drawing | |
const currentPoint = e.lngLat; | |
// Create the rectangle geometry dynamically | |
const rectangleCoords = [ | |
[startPoint.lng, startPoint.lat], | |
[currentPoint.lng, startPoint.lat], | |
[currentPoint.lng, currentPoint.lat], | |
[startPoint.lng, currentPoint.lat], | |
[startPoint.lng, startPoint.lat] // Close the loop | |
]; | |
const tempFeature = { | |
type: 'Feature', | |
id: 'drawing-preview', // Temporary ID | |
properties: {}, | |
geometry: { | |
type: 'Polygon', | |
coordinates: [rectangleCoords] // GeoJSON Polygon needs an array of rings | |
} | |
}; | |
// Update the source data with the temporary rectangle | |
const source = map.getSource(geojsonSourceId); | |
if (source) { | |
source.setData({ | |
type: 'FeatureCollection', | |
features: [tempFeature] | |
}); | |
} | |
} | |
function handleMouseUp(e) { | |
if (!isDrawing) return; // Only run if finishing a draw | |
isDrawing = false; | |
const endPoint = e.lngLat; | |
console.log('Drawing ended at:', endPoint); | |
// Restore map panning | |
map.dragPan.enable(); | |
// Restore cursor | |
map.getCanvas().style.cursor = ''; | |
// Check if start and end points are the same (simple click) | |
if (startPoint.lng === endPoint.lng && startPoint.lat === endPoint.lat) { | |
console.log("Drawing cancelled (start and end points are the same)."); | |
// Clear any temporary preview | |
const source = map.getSource(geojsonSourceId); | |
if (source) { | |
source.setData({ type: 'FeatureCollection', features: [] }); | |
} | |
startPoint = null; | |
return; // Don't create a zero-area rectangle | |
} | |
// Finalize the rectangle geometry | |
const finalCoords = [ | |
[startPoint.lng, startPoint.lat], | |
[endPoint.lng, startPoint.lat], | |
[endPoint.lng, endPoint.lat], | |
[startPoint.lng, endPoint.lat], | |
[startPoint.lng, startPoint.lat] // Close the loop | |
]; | |
const finalFeature = { | |
type: 'Feature', | |
id: Date.now(), // Use timestamp as a unique ID | |
properties: { | |
description: 'User drawn rectangle' | |
}, | |
geometry: { | |
type: 'Polygon', | |
coordinates: [finalCoords] | |
} | |
}; | |
// Update the source data with the final rectangle | |
const source = map.getSource(geojsonSourceId); | |
if (source) { | |
source.setData({ | |
type: 'FeatureCollection', | |
features: [finalFeature] | |
}); | |
// Make the outline solid now that it's finished | |
map.setPaintProperty(rectangleOutlineLayerId, 'line-dasharray', []); | |
} | |
// Store the ID of the created feature | |
currentRectangleFeatureId = finalFeature.id; | |
// Log the final GeoJSON geometry to the console | |
console.log("Rectangle Drawn (GeoJSON Geometry):", JSON.stringify(finalFeature.geometry, null, 2)); | |
// Enable the delete button | |
deleteButton.disabled = false; | |
// Clear the start point | |
startPoint = null; | |
} | |
// --- Deletion Logic --- | |
deleteButton.addEventListener('click', () => { | |
if (!currentRectangleFeatureId) return; // Nothing to delete | |
console.log('Deleting rectangle:', currentRectangleFeatureId); | |
// Clear the GeoJSON source | |
const source = map.getSource(geojsonSourceId); | |
if (source) { | |
source.setData({ | |
type: 'FeatureCollection', | |
features: [] // Set features to an empty array | |
}); | |
} | |
// Reset state | |
currentRectangleFeatureId = null; | |
// Disable the delete button again | |
deleteButton.disabled = true; | |
// Reset line style to dashed (optional, for next draw) | |
map.setPaintProperty(rectangleOutlineLayerId, 'line-dasharray', [2, 2]); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://jsfiddle.net/82qj716y/