MapSnippets logoMapSnippets logo

Geofencing

Define a polygon boundary and check point containment Each library has its own approach: here's how they compare.

Quick Recommendations

Leaflet — Best for simple geofence visualization where you use a lightweight point-in-polygon check with Leaflet's polygon rendering.

MapLibre GL JS — Ideal when geofence boundaries are part of a data-driven map with expressions-based styling for inside/outside states.

OpenLayers — The right choice when you need built-in geometry operations: OpenLayers has native polygon intersection and containment methods.

MapTiler SDK — Brings MapLibre's geofence approach, with simplified map initialization.

At a Glance

LeafletMapLibreOpenLayersSDK
Coordinates[lat, lng][lng, lat]fromLonLat([lng, lat])[lng, lat]
RenderingDOM + Canvas (2D)WebGL (GPU, 60fps)Canvas + WebGLWebGL (GPU, 60fps)
Bundle size~42 KB gzipped~250 KB gzipped~290 KB gzipped (tree-shakeable)~280 KB gzipped (includes MapLibre)
Learning curveEasiest (simple API, great tutorials)Moderate (source/layer model, expressions)Steepest (projections, OGC standards, verbose API)Easy (MapLibre API with convenience helpers)
3D supportNo native 3D (2D only)Full: terrain, fill-extrusion, globe, skyLimited: WebGL layers, no native globeFull (same as MapLibre)
Code lines22272528

Code Snippets

Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.

Leaflet22 linesShow code
arrow_forwardFull example
const map = L.map('map').setView([48.8566, 2.3522], 13);
L.tileLayer('https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', {
  tileSize: 256,
  attribution: '&copy; <a href="https://www.maptiler.com/">MapTiler</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);
const fence = [[48.865, 2.335], [48.865, 2.365], [48.845, 2.365], [48.845, 2.335]];
L.polygon(fence, { color: '#e63946', fillOpacity: 0.15, weight: 2 }).addTo(map).bindPopup('Geofence zone');
function pointInPolygon(lat, lng, poly) {
  let inside = false;
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
    const [yi, xi] = poly[i], [yj, xj] = poly[j];
    if ((yi > lng) !== (yj > lng) && lat < (xj - xi) * (lng - yi) / (yj - yi) + xi) inside = !inside;
  }
  return inside;
}
map.on('click', (e) => {
  const inside = pointInPolygon(e.latlng.lat, e.latlng.lng, fence);
  L.popup()
    .setLatLng(e.latlng)
    .setContent(inside ? '<b style="color:#4CAF50">Inside zone</b>' : '<b style="color:#e63946">Outside zone</b>')
    .openOn(map);
});
MapLibre GL JS27 linesShow code
arrow_forwardFull example
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.maptiler.com/maps/streets-v4/style.json?key=YOUR_MAPTILER_KEY',
  center: [14.42, 50.08],
  zoom: 13
});
const fenceCoords = [[14.40, 50.09], [14.44, 50.09], [14.44, 50.07], [14.40, 50.07], [14.40, 50.09]];
function pointInPolygon(lng, lat, ring) {
  let inside = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const [xi, yi] = ring[i], [xj, yj] = ring[j];
    if ((yi > lat) !== (yj > lat) && lng < (xj - xi) * (lat - yi) / (yj - yi) + xi) inside = !inside;
  }
  return inside;
}
map.on('load', () => {
  map.addSource('fence', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'Polygon', coordinates: [fenceCoords] } } });
  map.addLayer({ id: 'fence-fill', type: 'fill', source: 'fence', paint: { 'fill-color': '#e63946', 'fill-opacity': 0.15 } });
  map.addLayer({ id: 'fence-line', type: 'line', source: 'fence', paint: { 'line-color': '#e63946', 'line-width': 2 } });
});
map.on('click', (e) => {
  const inside = pointInPolygon(e.lngLat.lng, e.lngLat.lat, fenceCoords);
  new maplibregl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(inside ? '<b style="color:#4CAF50">Inside zone</b>' : '<b style="color:#e63946">Outside zone</b>')
    .addTo(map);
});
OpenLayers25 linesShow code
arrow_forwardFull example
var fenceCoords = [[13.38, 52.53], [13.43, 52.53], [13.43, 52.51], [13.38, 52.51], [13.38, 52.53]];
var fenceFeature = new ol.Feature({
  geometry: new ol.geom.Polygon([fenceCoords.map(function(c) { return ol.proj.fromLonLat(c); })])
});
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector({ features: [fenceFeature] }),
  style: new ol.style.Style({
    stroke: new ol.style.Stroke({ color: '#e63946', width: 2 }),
    fill: new ol.style.Fill({ color: 'rgba(230,57,70,0.15)' })
  })
});
var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', attributions: '&copy; <a href="https://www.maptiler.com/copyright/">MapTiler</a>' }) }),
    vectorLayer
  ],
  view: new ol.View({ center: ol.proj.fromLonLat([13.405, 52.52]), zoom: 13 })
});
map.on('click', function(evt) {
  var inside = fenceFeature.getGeometry().intersectsCoordinate(evt.coordinate);
  var info = document.getElementById('info');
  info.textContent = inside ? 'Inside zone' : 'Outside zone';
  info.style.color = inside ? '#4CAF50' : '#e63946';
});
MapTiler SDK28 linesShow code
arrow_forwardFull example
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
  container: 'map',
  style: maptilersdk.MapStyle.STREETS,
  center: [-0.1276, 51.5074],
  zoom: 13
});
const fenceCoords = [[-0.15, 51.52], [-0.10, 51.52], [-0.10, 51.50], [-0.15, 51.50], [-0.15, 51.52]];
function pointInPolygon(lng, lat, ring) {
  let inside = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const [xi, yi] = ring[i], [xj, yj] = ring[j];
    if ((yi > lat) !== (yj > lat) && lng < (xj - xi) * (lat - yi) / (yj - yi) + xi) inside = !inside;
  }
  return inside;
}
map.on('load', () => {
  map.addSource('fence', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'Polygon', coordinates: [fenceCoords] } } });
  map.addLayer({ id: 'fence-fill', type: 'fill', source: 'fence', paint: { 'fill-color': '#9C27B0', 'fill-opacity': 0.15 } });
  map.addLayer({ id: 'fence-line', type: 'line', source: 'fence', paint: { 'line-color': '#9C27B0', 'line-width': 2 } });
});
map.on('click', (e) => {
  const inside = pointInPolygon(e.lngLat.lng, e.lngLat.lat, fenceCoords);
  new maptilersdk.Popup()
    .setLngLat(e.lngLat)
    .setHTML(inside ? '<b style="color:#4CAF50">Inside zone</b>' : '<b style="color:#e63946">Outside zone</b>')
    .addTo(map);
});

Things to Know

Implementation-specific details that matter for geofencing.

Leaflet

No built-in point-in-polygon test: you need a ray-casting algorithm or a library like Turf.js for containment checks.

MapLibre GL JS

Point-in-polygon logic must be implemented in JavaScript (ray-casting or Turf.js); the polygon itself is a fill/line layer.

OpenLayers

ol.geom.Polygon has a built-in intersectsCoordinate() method that handles containment checks natively.

MapTiler SDK

Same approach as MapLibre: manual point-in-polygon check with the boundary rendered as a GeoJSON fill layer.

Which Library to Use?

auto_awesome

Build Maps with AI

Generate code like these examples using mapping library skills in your IDE.