Skip to content

Layers

Learn how to work with different layer types and dynamic styling.

Fill Layer

Create areas with fill styling:

vue
<template>
  <Maplibre :options="mapOptions" style="height: 400px;">
    <GeoJsonSource :data="polygonData" source-id="polygons">
      <FillLayer :style="fillStyle" />
    </GeoJsonSource>
  </Maplibre>
</template>

<script setup>
import { ref } from 'vue';
import { Maplibre, GeoJsonSource, FillLayer } from 'vue3-maplibre-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [-74.006, 40.7128],
  zoom: 10,
});

const polygonData = ref({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Polygon',
        coordinates: [
          [
            [-74.0059, 40.7128],
            [-74.0059, 40.7589],
            [-73.9352, 40.7589],
            [-73.9352, 40.7128],
            [-74.0059, 40.7128],
          ],
        ],
      },
      properties: {
        name: 'Manhattan Area',
      },
    },
  ],
});

const fillStyle = ref({
  'fill-color': '#088',
  'fill-opacity': 0.6,
  'fill-outline-color': '#fff',
});
</script>

Circle Layer

Display points as circles with customizable styling:

vue
<template>
  <div>
    <div class="controls">
      <label>Circle Size: {{ circleRadius }}</label>
      <input type="range" min="5" max="50" v-model="circleRadius" />
    </div>

    <Maplibre :options="mapOptions" style="height: 400px;">
      <GeoJsonSource :data="pointData" source-id="points">
        <CircleLayer :style="circleStyle" @click="onCircleClick" />
      </GeoJsonSource>
    </Maplibre>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { Maplibre, GeoJsonSource, CircleLayer } from 'vue3-maplibre-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [0, 0],
  zoom: 2,
});

const circleRadius = ref(15);

const pointData = ref({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [0, 0] },
      properties: { name: 'Point 1', value: 100 },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-74.006, 40.7128] },
      properties: { name: 'Point 2', value: 200 },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [2.3522, 48.8566] },
      properties: { name: 'Point 3', value: 150 },
    },
  ],
});

const circleStyle = computed(() => ({
  'circle-radius': circleRadius.value,
  'circle-color': [
    'interpolate',
    ['linear'],
    ['get', 'value'],
    100,
    '#ff6b6b',
    150,
    '#feca57',
    200,
    '#48dbfb',
  ],
  'circle-stroke-width': 2,
  'circle-stroke-color': '#ffffff',
}));

function onCircleClick(event) {
  const feature = event.features[0];
  alert(
    `Clicked on ${feature.properties.name} (Value: ${feature.properties.value})`,
  );
}
</script>

<style scoped>
.controls {
  margin-bottom: 10px;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 4px;
}

.controls label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.controls input {
  width: 100%;
}
</style>

Line Layer

Create lines and paths:

vue
<template>
  <div>
    <div class="controls">
      <button @click="animateLine">Animate Line Drawing</button>
      <button @click="resetLine">Reset</button>
    </div>

    <Maplibre :options="mapOptions" style="height: 400px;">
      <GeoJsonSource :data="lineData" source-id="route">
        <LineLayer :style="lineStyle" />
      </GeoJsonSource>
    </Maplibre>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { Maplibre, GeoJsonSource, LineLayer } from 'vue3-maplibre-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [-74.006, 40.7128],
  zoom: 12,
});

const fullRoute = [
  [-74.006, 40.7128],
  [-73.9857, 40.7589],
  [-73.9665, 40.7812],
  [-73.9441, 40.8176],
  [-73.9297, 40.8448],
];

const lineData = ref({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: [],
      },
      properties: {},
    },
  ],
});

const lineStyle = ref({
  'line-color': '#007cbf',
  'line-width': 4,
  'line-opacity': 0.8,
});

function animateLine() {
  const coordinates = [];
  let index = 0;

  const addPoint = () => {
    if (index < fullRoute.length) {
      coordinates.push(fullRoute[index]);
      lineData.value.features[0].geometry.coordinates = [...coordinates];
      index++;
      setTimeout(addPoint, 500);
    }
  };

  addPoint();
}

function resetLine() {
  lineData.value.features[0].geometry.coordinates = [];
}
</script>

<style scoped>
.controls {
  margin-bottom: 10px;
  display: flex;
  gap: 10px;
}

.controls button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background: #007cbf;
  color: white;
}

.controls button:hover {
  background: #005a8b;
}
</style>

Symbol Layer

Add text and icon symbols:

vue
<template>
  <Maplibre :options="mapOptions" style="height: 400px;">
    <GeoJsonSource :data="symbolData" source-id="symbols">
      <SymbolLayer :style="symbolStyle" />
    </GeoJsonSource>
  </Maplibre>
</template>

<script setup>
import { ref } from 'vue';
import { Maplibre, GeoJsonSource, SymbolLayer } from 'vue3-maplibre-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [-74.006, 40.7128],
  zoom: 10,
});

const symbolData = ref({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-74.006, 40.7128] },
      properties: {
        name: 'New York City',
        type: 'city',
      },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-73.9857, 40.7589] },
      properties: {
        name: 'Times Square',
        type: 'landmark',
      },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-73.9665, 40.7812] },
      properties: {
        name: 'Central Park',
        type: 'park',
      },
    },
  ],
});

const symbolStyle = ref({
  'text-field': ['get', 'name'],
  'text-font': ['Open Sans Regular'],
  'text-size': 14,
  'text-color': '#333',
  'text-halo-color': '#fff',
  'text-halo-width': 2,
  'text-offset': [0, 1.5],
  'text-anchor': 'top',
});
</script>

Multiple Layers

Combine different layer types:

vue
<template>
  <div>
    <div class="layer-controls">
      <label>
        <input type="checkbox" v-model="showFill" />
        Show Fill Layer
      </label>
      <label>
        <input type="checkbox" v-model="showCircles" />
        Show Circle Layer
      </label>
      <label>
        <input type="checkbox" v-model="showLabels" />
        Show Labels
      </label>
    </div>

    <Maplibre :options="mapOptions" style="height: 400px;">
      <GeoJsonSource :data="combinedData" source-id="combined">
        <FillLayer v-if="showFill" :style="fillStyle" />
        <CircleLayer v-if="showCircles" :style="circleStyle" />
        <SymbolLayer v-if="showLabels" :style="symbolStyle" />
      </GeoJsonSource>
    </Maplibre>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import {
  Maplibre,
  GeoJsonSource,
  FillLayer,
  CircleLayer,
  SymbolLayer,
} from 'vue3-maplibre-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [-74.006, 40.7128],
  zoom: 11,
});

const showFill = ref(true);
const showCircles = ref(true);
const showLabels = ref(true);

const combinedData = ref({
  type: 'FeatureCollection',
  features: [
    // Polygon feature
    {
      type: 'Feature',
      geometry: {
        type: 'Polygon',
        coordinates: [
          [
            [-74.0059, 40.7128],
            [-74.0059, 40.7589],
            [-73.9352, 40.7589],
            [-73.9352, 40.7128],
            [-74.0059, 40.7128],
          ],
        ],
      },
      properties: {
        name: 'Manhattan',
        type: 'area',
      },
    },
    // Point features
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-74.006, 40.7128] },
      properties: { name: 'Financial District', type: 'district' },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-73.9857, 40.7589] },
      properties: { name: 'Times Square', type: 'landmark' },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [-73.9665, 40.7812] },
      properties: { name: 'Central Park', type: 'park' },
    },
  ],
});

const fillStyle = ref({
  'fill-color': '#088',
  'fill-opacity': 0.3,
});

const circleStyle = ref({
  'circle-radius': [
    'case',
    ['==', ['get', 'type'], 'landmark'],
    8,
    ['==', ['get', 'type'], 'park'],
    6,
    4,
  ],
  'circle-color': [
    'case',
    ['==', ['get', 'type'], 'landmark'],
    '#ff6b6b',
    ['==', ['get', 'type'], 'park'],
    '#4ecdc4',
    '#45b7d1',
  ],
  'circle-stroke-width': 2,
  'circle-stroke-color': '#ffffff',
});

const symbolStyle = ref({
  'text-field': ['get', 'name'],
  'text-font': ['Open Sans Regular'],
  'text-size': 12,
  'text-color': '#333',
  'text-halo-color': '#fff',
  'text-halo-width': 1,
  'text-offset': [0, 1.5],
  'text-anchor': 'top',
});
</script>

<style scoped>
.layer-controls {
  margin-bottom: 10px;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 4px;
  display: flex;
  gap: 20px;
}

.layer-controls label {
  display: flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
}
</style>

Key Features

  • Multiple Layer Types: Fill, Circle, Line, and Symbol layers
  • Dynamic Styling: Reactive style updates
  • Data-Driven Styling: Style based on feature properties
  • Layer Control: Show/hide layers dynamically
  • Event Handling: Click and hover events on layers

Released under the MIT License.