How to Style GeoJSON Lines and Polygons

API or service
GeoJSON + JavaScript map libraries
Task
GeoJSON geometry → styled map layer
Examples
LeafletMapLibre GLOpenLayers
Difficulty
Intermediate
Time
10 min

You have GeoJSON lines or polygons—such as routes, service areas, administrative boundaries, or delivery zones—and need to control how they appear on an interactive map. The geometry remains the same across map libraries, but each renderer has its own styling API.

Keep visualization properties in application code or derive them from GeoJSON feature properties. This makes it possible to use a shared dataset while applying a library-specific line color, width, fill, opacity, or data-driven style.

Prepare the GeoJSON

GeoJSON coordinates always use [longitude, latitude] order. A polygon ring must repeat its first coordinate at the end so the ring is closed.

const geojson = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: { kind: "route", color: "#1565c0" },
      geometry: {
        type: "LineString",
        coordinates: [
          [13.3777, 52.5163],
          [13.3904, 52.5208],
          [13.4010, 52.5169]
        ]
      }
    },
    {
      type: "Feature",
      properties: { kind: "area", color: "#43a047" },
      geometry: {
        type: "Polygon",
        coordinates: [[
          [13.3900, 52.5140],
          [13.4080, 52.5140],
          [13.4080, 52.5230],
          [13.3900, 52.5230],
          [13.3900, 52.5140]
        ]]
      }
    }
  ]
};

The following implementations assume that the map and this geojson object already exist.

Style GeoJSON with Leaflet

L.geoJSON() converts the features into Leaflet vector layers. Its style callback runs for lines and polygons and can read each feature’s properties.

const geometryLayer = L.geoJSON(geojson, {
  style(feature) {
    const isPolygon = feature.geometry.type === "Polygon";

    return {
      color: feature.properties.color,
      weight: isPolygon ? 2 : 5,
      opacity: 0.9,
      fillColor: feature.properties.color,
      fillOpacity: isPolygon ? 0.25 : 0
    };
  }
}).addTo(map);

map.fitBounds(geometryLayer.getBounds(), { padding: [24, 24] });

Leaflet uses one path-style object for both geometry types. LineString features ignore fill properties, while Polygon features use both stroke and fill properties.

Style GeoJSON with MapLibre GL

Add the GeoJSON once, then render it through separate line and fill layers. The geometry filters ensure that each layer receives the appropriate feature type.

map.on("load", () => {
  map.addSource("application-geometry", {
    type: "geojson",
    data: geojson
  });

  map.addLayer({
    id: "application-areas",
    type: "fill",
    source: "application-geometry",
    filter: ["==", ["geometry-type"], "Polygon"],
    paint: {
      "fill-color": ["get", "color"],
      "fill-opacity": 0.25,
      "fill-outline-color": ["get", "color"]
    }
  });

  map.addLayer({
    id: "application-lines",
    type: "line",
    source: "application-geometry",
    filter: ["==", ["geometry-type"], "LineString"],
    paint: {
      "line-color": ["get", "color"],
      "line-width": 5,
      "line-opacity": 0.9
    }
  });
});

MapLibre expressions such as ["get", "color"] make the style data-driven. Use map.setPaintProperty() when the application needs to change a layer after it has been created.

Style GeoJSON with OpenLayers

Read the GeoJSON into the projection used by the map, then return an OpenLayers style based on the geometry and feature properties.

import GeoJSON from "ol/format/GeoJSON.js";
import VectorLayer from "ol/layer/Vector.js";
import VectorSource from "ol/source/Vector.js";
import Fill from "ol/style/Fill.js";
import Stroke from "ol/style/Stroke.js";
import Style from "ol/style/Style.js";

const source = new VectorSource({
  features: new GeoJSON().readFeatures(geojson, {
    featureProjection: "EPSG:3857"
  })
});

const geometryLayer = new VectorLayer({
  source,
  style(feature) {
    const color = feature.get("color");
    const isPolygon = feature.getGeometry().getType() === "Polygon";

    return new Style({
      stroke: new Stroke({
        color,
        width: isPolygon ? 2 : 5
      }),
      fill: new Fill({
        color: isPolygon ? `${color}40` : "transparent"
      })
    });
  }
});

map.addLayer(geometryLayer);
map.getView().fit(source.getExtent(), { padding: [24, 24, 24, 24] });

The 40 suffix gives a six-digit hexadecimal color approximately 25% opacity. If your input already uses the map projection, omit featureProjection; standard GeoJSON normally uses geographic longitude and latitude.