Skip to content

Commit

Permalink
fix: simplify diff map style, add polygon
Browse files Browse the repository at this point in the history
  • Loading branch information
frodrigo authored and wazolab committed Jan 9, 2025
1 parent 92d6c89 commit d8be9c3
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 122 deletions.
3 changes: 0 additions & 3 deletions src/components/MapFilters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ function setPreset(index: number) {
>
<button>{{ preset.title }}</button>
</li>
<li>
...
</li>
</ul>
</aside>
</template>
Expand Down
262 changes: 143 additions & 119 deletions src/composables/useMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,55 @@ import { loChaColors, useLoCha } from '@/composables/useLoCha'
import { LngLatBounds, Map, NavigationControl, Popup } from 'maplibre-gl'
import { ref, shallowRef } from 'vue'

/**
* Interface defining the map functionality.
*/
export interface IMap {
/**
* Initializes the map and sets up necessary controls and layers.
* @param emits - The emits object for emitting events.
*/
init: (emits: MapEmits) => void

/**
* Handles the update of map data and adjusts the map view accordingly.
* @param data - The data to update the map with.
*/
handleMapDataUpdate: (data: ApiResponse) => void
}

/**
* Type definition for the map event emitters.
* This is used to emit various map-related events.
*/
export interface MapEmits {
(e: 'error', payload: Error): void
(e: 'update-bbox', bbox: string): void
}

const SOURCE_ID = 'lochas'
const MAP_STYLE_URL = 'https://vecto.teritorio.xyz/styles/teritorio-tourism-latest/style.json?key=teritorio-demo-1-eTuhasohVahquais0giuth7i'
const MAP_STYLE_URL = 'https://vecto-dev.teritorio.xyz/styles/positron/style.json?key=teritorio-demo-1-eTuhasohVahquais0giuth7i'
const LAYERS = {
LineString: 'feature-lines',
LineStringAfter: 'feature-lines-after',
LineStringBefore: 'feature-lines-before',
Polygon: 'feature-polygons',
Line: 'feature-lines',
Point: 'feature-points',
PointBefore: 'feature-points-before',
PointAfter: 'feature-points-after',
}

const lineBasePaint = {
'line-width': 2,
}
const lineUpdatePaint = {
...lineBasePaint,
'line-color': loChaColors.update,
'line-dasharray': [4, 2],
}
const pointBasePaint = {
'circle-radius': 12,
'circle-stroke-color': '#000000',
'circle-stroke-width': 4,
}
const pointUpdatePaint = {
...pointBasePaint,
'circle-color': loChaColors.update,
}

let emit: MapEmits
const map = shallowRef<Map>()
const source = ref<GeoJSONSource>()

/**
* Provides methods to initialize and manage the map.
* @returns The map-related functions such as `init` and `handleMapDataUpdate`.
*/
export function useMap(): IMap {
const { featureCount, loCha } = useLoCha()

/**
* Initializes the map, sets up the event listeners, and configures layers and sources.
* @param emits - The emits object used for emitting map events.
*/
function init(emits: MapEmits): void {
emit = emits

Expand All @@ -64,9 +67,7 @@ export function useMap(): IMap {
map.value.addControl(new NavigationControl())

map.value.on('load', () => {
map.value?.on('click', _openPopup)

map.value?.on('moveend', _updateBoundingBox)
_setEventListeners()

map.value?.addSource(SOURCE_ID, {
type: 'geojson',
Expand All @@ -76,83 +77,153 @@ export function useMap(): IMap {
},
})

if (loCha.value?.bbox) {
map.value?.fitBounds(new LngLatBounds(loCha.value.bbox as [number, number, number, number]), { padding: 20 })
}

source.value = map.value?.getSource(SOURCE_ID)

_setupMapLayers()
})
}

/**
* Updates the map data and adjusts the view accordingly.
* If features are found, the map will zoom to the bounding box of the features.
* Otherwise, it will reset the map to the initial view.
* @param data - The map data to be set.
*/
function handleMapDataUpdate(data: ApiResponse): void {
if (!map.value)
throw new Error('Call useMap.init() function first.')

if (!source.value)
throw new Error('Map data source is missing.')

source.value.setData(data)

if (featureCount.value && data.bbox) {
map.value.fitBounds(new LngLatBounds(data.bbox as [number, number, number, number]), { padding: 20 })
}
else {
map.value.setCenter([0, 0])
map.value.setZoom(0)
emit('error', { message: '0 changes have been found!', type: 'warning' })
}
}

/**
* Sets up the event listeners for the map.
* Includes handling for map click, hover, and movement.
*/
function _setEventListeners(): void {
map.value?.on('click', _openPopup)

map.value?.on('mouseenter', LAYERS.Point, () => {
if (!map.value)
throw new Error('Call useMap.init() function first.')

map.value.getCanvas().style.cursor = 'pointer'
})
map.value?.on('mouseenter', LAYERS.Line, () => {
if (!map.value)
throw new Error('Call useMap.init() function first.')

map.value.getCanvas().style.cursor = 'pointer'
})

map.value?.on('mouseleave', LAYERS.Point, () => {
if (!map.value)
throw new Error('Call useMap.init() function first.')

map.value.getCanvas().style.cursor = ''
})
map.value?.on('mouseleave', LAYERS.Line, () => {
if (!map.value)
throw new Error('Call useMap.init() function first.')

map.value.getCanvas().style.cursor = ''
})

map.value?.on('moveend', _updateBoundingBox)
}

/**
* Opens a popup when a feature is clicked on the map.
* It displays the feature's ID at the coordinates of the feature.
* @param e - The MapMouseEvent triggered by the click.
*/
function _openPopup(e: MapMouseEvent): void {
if (!map.value)
throw new Error('Call useMap.init() function first.')

const features = map.value.queryRenderedFeatures(e.point, {
layers: [LAYERS.Point, LAYERS.PointAfter, LAYERS.PointBefore],
layers: Object.values(LAYERS),
})

if (!features.length || features[0].geometry.type !== 'Point')
if (!features.length)
return

new Popup()
.setLngLat(features[0].geometry.coordinates as [number, number])
.setHTML(features[0].properties.id.toString())
.setLngLat(e.lngLat)
.setHTML(`${features[0].properties.objtype}-${features[0].properties.id}-v${features[0].properties.version}`)
.addTo(map.value)
}

function _setupMapLayers(): void {
if (!map.value)
throw new Error('Call useMap.init() function first.')

// LineString type
// Polygon type
map.value.addLayer({
id: LAYERS.LineString,
type: 'line',
id: LAYERS.Polygon,
type: 'fill',
source: SOURCE_ID,
paint: {
...lineBasePaint,
'line-color': [
'fill-opacity': 0.5,
'fill-color': [
'case',
['==', ['get', 'is_created'], true],
loChaColors.create,
['==', ['get', 'is_deleted'], true],
loChaColors.delete,
'#000000',
['==', ['get', 'is_before'], true],
'#ce7e00',
loChaColors.update,
],
},
filter: [
'all',
['==', ['geometry-type'], 'LineString'],
['!=', ['get', 'is_after'], true],
['!=', ['get', 'is_before'], true],
],
filter: ['in', ['geometry-type'], ['literal', ['Polygon', 'MultiPolygon']]],
})

// LineString type
map.value.addLayer({
id: LAYERS.LineStringBefore,
id: LAYERS.Line,
type: 'line',
source: SOURCE_ID,
paint: {
...lineUpdatePaint,
'line-opacity': 0.5,
'line-offset': 8,
'line-width': 4,
'line-opacity': [
'case',
['==', ['get', 'is_created'], true],
1,
['==', ['get', 'is_deleted'], true],
1,
['==', ['get', 'is_before'], true],
1,
0.5,
],
'line-color': [
'case',
['==', ['get', 'is_created'], true],
loChaColors.create,
['==', ['get', 'is_deleted'], true],
loChaColors.delete,
['==', ['get', 'is_before'], true],
'#ce7e00',
loChaColors.update,
],
},
filter: [
'all',
['==', ['geometry-type'], 'LineString'],
['==', ['get', 'is_before'], true],
],
})

map.value.addLayer({
id: LAYERS.LineStringAfter,
type: 'line',
source: SOURCE_ID,
paint: lineUpdatePaint,
filter: [
'all',
['==', ['geometry-type'], 'LineString'],
['==', ['get', 'is_after'], true],
],
filter: ['in', ['geometry-type'], ['literal', ['LineString', 'MultiLineString']]],
})

// Point type
Expand All @@ -161,72 +232,25 @@ export function useMap(): IMap {
type: 'circle',
source: SOURCE_ID,
paint: {
...pointBasePaint,
'circle-radius': 12,
'circle-stroke-color': '#000000',
'circle-stroke-width': 2,
'circle-opacity': 0.5,
'circle-color': [
'case',
['==', ['get', 'is_created'], true],
loChaColors.create,
['==', ['get', 'is_deleted'], true],
loChaColors.delete,
'#000000',
['==', ['get', 'is_before'], true],
'#ce7e00',
loChaColors.update,
],
},
filter: [
'all',
['==', ['geometry-type'], 'Point'],
['!=', ['get', 'is_after'], true],
['!=', ['get', 'is_before'], true],
],
})

map.value.addLayer({
id: LAYERS.PointBefore,
type: 'circle',
source: SOURCE_ID,
paint: {
...pointUpdatePaint,
'circle-opacity': 0.5,
'circle-translate': [28, 0],
},
filter: [
'all',
['==', ['geometry-type'], 'Point'],
['==', ['get', 'is_before'], true],
],
})

map.value.addLayer({
id: LAYERS.PointAfter,
type: 'circle',
source: SOURCE_ID,
paint: pointBasePaint,
filter: [
'all',
['==', ['geometry-type'], 'Point'],
['==', ['get', 'is_after'], true],
],
filter: ['in', ['geometry-type'], ['literal', ['Point', 'MultiPoint']]],
})
}

function handleMapDataUpdate(data: ApiResponse): void {
if (!map.value)
throw new Error('Call useMap.init() function first.')

if (!source.value)
throw new Error('Map data source is missing.')

source.value.setData(data)

if (featureCount.value && data.bbox) {
map.value.fitBounds(new LngLatBounds(data.bbox as [number, number, number, number]), { padding: 20 })
}
else {
map.value.setCenter([0, 0])
map.value.setZoom(0)
emit('error', { message: '0 changes have been found!', type: 'warning' })
}
}

function _updateBoundingBox(): void {
if (!map.value)
throw new Error('Call useMap.init() function first.')
Expand Down

0 comments on commit d8be9c3

Please sign in to comment.