Skip to content

Instantly share code, notes, and snippets.

@do-me
Created April 17, 2025 13:58
Show Gist options
  • Save do-me/3397b78c40b2e0e751ecfc17b6254f27 to your computer and use it in GitHub Desktop.
Save do-me/3397b78c40b2e0e751ecfc17b6254f27 to your computer and use it in GitHub Desktop.
maplibre custom rectangle drawing logic
<!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>
@do-me
Copy link
Author

do-me commented Apr 17, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment