feat(map): add server-side h3 clustering and hover highlight
All checks were successful
Build Docker Image / build (push) Successful in 4m55s
All checks were successful
Build Docker Image / build (push) Successful in 4m55s
- Add useClusteredNodes composable for fetching clustered nodes - Update CatalogMap to support server-side clustering mode - Add bounds-change event for fetching clusters on map move/zoom - Add hover event to HubCard for marker highlighting - Update hubs/map page to use new clustering system
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Map as MapboxMapType } from 'mapbox-gl'
|
import type { Map as MapboxMapType } from 'mapbox-gl'
|
||||||
import { LngLatBounds } from 'mapbox-gl'
|
import { LngLatBounds } from 'mapbox-gl'
|
||||||
|
import type { ClusterPointType } from '~/composables/graphql/public/geo-generated'
|
||||||
|
|
||||||
interface MapItem {
|
interface MapItem {
|
||||||
uuid: string
|
uuid: string
|
||||||
@@ -26,25 +27,41 @@ interface MapItem {
|
|||||||
country?: string
|
country?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MapBounds {
|
||||||
|
west: number
|
||||||
|
south: number
|
||||||
|
east: number
|
||||||
|
north: number
|
||||||
|
zoom: number
|
||||||
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
mapId: string
|
mapId: string
|
||||||
items: MapItem[]
|
items?: MapItem[]
|
||||||
|
clusteredPoints?: ClusterPointType[]
|
||||||
|
useServerClustering?: boolean
|
||||||
|
hoveredItemId?: string | null
|
||||||
pointColor?: string
|
pointColor?: string
|
||||||
initialCenter?: [number, number]
|
initialCenter?: [number, number]
|
||||||
initialZoom?: number
|
initialZoom?: number
|
||||||
}>(), {
|
}>(), {
|
||||||
pointColor: '#10b981',
|
pointColor: '#10b981',
|
||||||
initialCenter: () => [37.64, 55.76],
|
initialCenter: () => [37.64, 55.76],
|
||||||
initialZoom: 2
|
initialZoom: 2,
|
||||||
|
useServerClustering: false,
|
||||||
|
items: () => [],
|
||||||
|
clusteredPoints: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'select-item': [uuid: string]
|
'select-item': [uuid: string]
|
||||||
|
'bounds-change': [bounds: MapBounds]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const mapRef = useMapboxRef(props.mapId)
|
const mapRef = useMapboxRef(props.mapId)
|
||||||
const { flyThroughSpace } = useMapboxFlyAnimation()
|
const { flyThroughSpace } = useMapboxFlyAnimation()
|
||||||
const didFitBounds = ref(false)
|
const didFitBounds = ref(false)
|
||||||
|
const mapInitialized = ref(false)
|
||||||
|
|
||||||
const mapOptions = computed(() => ({
|
const mapOptions = computed(() => ({
|
||||||
style: 'mapbox://styles/mapbox/satellite-streets-v12',
|
style: 'mapbox://styles/mapbox/satellite-streets-v12',
|
||||||
@@ -54,6 +71,7 @@ const mapOptions = computed(() => ({
|
|||||||
pitch: 20
|
pitch: 20
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Client-side clustering GeoJSON (when not using server clustering)
|
||||||
const geoJsonData = computed(() => ({
|
const geoJsonData = computed(() => ({
|
||||||
type: 'FeatureCollection' as const,
|
type: 'FeatureCollection' as const,
|
||||||
features: props.items.map(item => ({
|
features: props.items.map(item => ({
|
||||||
@@ -63,8 +81,39 @@ const geoJsonData = computed(() => ({
|
|||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Server-side clustering GeoJSON
|
||||||
|
const serverClusteredGeoJson = computed(() => ({
|
||||||
|
type: 'FeatureCollection' as const,
|
||||||
|
features: (props.clusteredPoints || []).filter(Boolean).map(point => ({
|
||||||
|
type: 'Feature' as const,
|
||||||
|
properties: {
|
||||||
|
id: point!.id,
|
||||||
|
name: point!.name,
|
||||||
|
count: point!.count ?? 1,
|
||||||
|
expansionZoom: point!.expansionZoom,
|
||||||
|
isCluster: (point!.count ?? 1) > 1
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: 'Point' as const,
|
||||||
|
coordinates: [point!.longitude ?? 0, point!.latitude ?? 0]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
const sourceId = computed(() => `${props.mapId}-points`)
|
const sourceId = computed(() => `${props.mapId}-points`)
|
||||||
|
|
||||||
|
const emitBoundsChange = (map: MapboxMapType) => {
|
||||||
|
const bounds = map.getBounds()
|
||||||
|
if (!bounds) return
|
||||||
|
emit('bounds-change', {
|
||||||
|
west: bounds.getWest(),
|
||||||
|
south: bounds.getSouth(),
|
||||||
|
east: bounds.getEast(),
|
||||||
|
north: bounds.getNorth(),
|
||||||
|
zoom: map.getZoom()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onMapCreated = (map: MapboxMapType) => {
|
const onMapCreated = (map: MapboxMapType) => {
|
||||||
const initMap = () => {
|
const initMap = () => {
|
||||||
map.setFog({
|
map.setFog({
|
||||||
@@ -75,103 +124,19 @@ const onMapCreated = (map: MapboxMapType) => {
|
|||||||
'star-intensity': 0.6
|
'star-intensity': 0.6
|
||||||
})
|
})
|
||||||
|
|
||||||
map.addSource(sourceId.value, {
|
if (props.useServerClustering) {
|
||||||
type: 'geojson',
|
initServerClusteringLayers(map)
|
||||||
data: geoJsonData.value,
|
} else {
|
||||||
cluster: true,
|
initClientClusteringLayers(map)
|
||||||
clusterMaxZoom: 14,
|
|
||||||
clusterRadius: 50
|
|
||||||
})
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: 'clusters',
|
|
||||||
type: 'circle',
|
|
||||||
source: sourceId.value,
|
|
||||||
filter: ['has', 'point_count'],
|
|
||||||
paint: {
|
|
||||||
'circle-color': props.pointColor,
|
|
||||||
'circle-radius': ['step', ['get', 'point_count'], 20, 10, 30, 50, 40],
|
|
||||||
'circle-opacity': 0.8,
|
|
||||||
'circle-stroke-width': 2,
|
|
||||||
'circle-stroke-color': '#ffffff'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: 'cluster-count',
|
|
||||||
type: 'symbol',
|
|
||||||
source: sourceId.value,
|
|
||||||
filter: ['has', 'point_count'],
|
|
||||||
layout: {
|
|
||||||
'text-field': ['get', 'point_count_abbreviated'],
|
|
||||||
'text-size': 14
|
|
||||||
},
|
|
||||||
paint: { 'text-color': '#ffffff' }
|
|
||||||
})
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: 'unclustered-point',
|
|
||||||
type: 'circle',
|
|
||||||
source: sourceId.value,
|
|
||||||
filter: ['!', ['has', 'point_count']],
|
|
||||||
paint: {
|
|
||||||
'circle-radius': 12,
|
|
||||||
'circle-color': props.pointColor,
|
|
||||||
'circle-stroke-width': 3,
|
|
||||||
'circle-stroke-color': '#ffffff'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: 'point-labels',
|
|
||||||
type: 'symbol',
|
|
||||||
source: sourceId.value,
|
|
||||||
filter: ['!', ['has', 'point_count']],
|
|
||||||
layout: {
|
|
||||||
'text-field': ['get', 'name'],
|
|
||||||
'text-offset': [0, 1.5],
|
|
||||||
'text-size': 12,
|
|
||||||
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold']
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'text-color': '#ffffff',
|
|
||||||
'text-halo-color': '#000000',
|
|
||||||
'text-halo-width': 1.5
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
map.on('click', 'clusters', (e) => {
|
|
||||||
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] })
|
|
||||||
if (!features.length) return
|
|
||||||
const clusterId = features[0].properties?.cluster_id
|
|
||||||
const source = map.getSource(sourceId.value) as mapboxgl.GeoJSONSource
|
|
||||||
source.getClusterExpansionZoom(clusterId, (err, zoom) => {
|
|
||||||
if (err) return
|
|
||||||
const geometry = features[0].geometry as GeoJSON.Point
|
|
||||||
map.easeTo({ center: geometry.coordinates as [number, number], zoom: zoom || 4 })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
map.on('click', 'unclustered-point', (e) => {
|
|
||||||
const features = map.queryRenderedFeatures(e.point, { layers: ['unclustered-point'] })
|
|
||||||
if (!features.length) return
|
|
||||||
emit('select-item', features[0].properties?.uuid)
|
|
||||||
})
|
|
||||||
|
|
||||||
map.on('mouseenter', 'clusters', () => { map.getCanvas().style.cursor = 'pointer' })
|
|
||||||
map.on('mouseleave', 'clusters', () => { map.getCanvas().style.cursor = '' })
|
|
||||||
map.on('mouseenter', 'unclustered-point', () => { map.getCanvas().style.cursor = 'pointer' })
|
|
||||||
map.on('mouseleave', 'unclustered-point', () => { map.getCanvas().style.cursor = '' })
|
|
||||||
|
|
||||||
// Auto-fit bounds to all items
|
|
||||||
if (!didFitBounds.value && props.items.length > 0) {
|
|
||||||
const bounds = new LngLatBounds()
|
|
||||||
props.items.forEach(item => {
|
|
||||||
bounds.extend([item.longitude, item.latitude])
|
|
||||||
})
|
|
||||||
map.fitBounds(bounds, { padding: 50, maxZoom: 10 })
|
|
||||||
didFitBounds.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit initial bounds
|
||||||
|
emitBoundsChange(map)
|
||||||
|
|
||||||
|
// Emit bounds on move/zoom end
|
||||||
|
map.on('moveend', () => emitBoundsChange(map))
|
||||||
|
|
||||||
|
mapInitialized.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.loaded()) {
|
if (map.loaded()) {
|
||||||
@@ -181,15 +146,232 @@ const onMapCreated = (map: MapboxMapType) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update map data when items change
|
const initClientClusteringLayers = (map: MapboxMapType) => {
|
||||||
watch(geoJsonData, (newData) => {
|
map.addSource(sourceId.value, {
|
||||||
if (!mapRef.value) return
|
type: 'geojson',
|
||||||
|
data: geoJsonData.value,
|
||||||
|
cluster: true,
|
||||||
|
clusterMaxZoom: 14,
|
||||||
|
clusterRadius: 50
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'clusters',
|
||||||
|
type: 'circle',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['has', 'point_count'],
|
||||||
|
paint: {
|
||||||
|
'circle-color': props.pointColor,
|
||||||
|
'circle-radius': ['step', ['get', 'point_count'], 20, 10, 30, 50, 40],
|
||||||
|
'circle-opacity': 0.8,
|
||||||
|
'circle-stroke-width': 2,
|
||||||
|
'circle-stroke-color': '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'cluster-count',
|
||||||
|
type: 'symbol',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['has', 'point_count'],
|
||||||
|
layout: {
|
||||||
|
'text-field': ['get', 'point_count_abbreviated'],
|
||||||
|
'text-size': 14
|
||||||
|
},
|
||||||
|
paint: { 'text-color': '#ffffff' }
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'unclustered-point',
|
||||||
|
type: 'circle',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['!', ['has', 'point_count']],
|
||||||
|
paint: {
|
||||||
|
'circle-radius': 12,
|
||||||
|
'circle-color': [
|
||||||
|
'case',
|
||||||
|
['==', ['get', 'uuid'], props.hoveredItemId || ''],
|
||||||
|
'#facc15', // yellow when hovered
|
||||||
|
props.pointColor
|
||||||
|
],
|
||||||
|
'circle-stroke-width': 3,
|
||||||
|
'circle-stroke-color': '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'point-labels',
|
||||||
|
type: 'symbol',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['!', ['has', 'point_count']],
|
||||||
|
layout: {
|
||||||
|
'text-field': ['get', 'name'],
|
||||||
|
'text-offset': [0, 1.5],
|
||||||
|
'text-size': 12,
|
||||||
|
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold']
|
||||||
|
},
|
||||||
|
paint: {
|
||||||
|
'text-color': '#ffffff',
|
||||||
|
'text-halo-color': '#000000',
|
||||||
|
'text-halo-width': 1.5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('click', 'clusters', (e) => {
|
||||||
|
const features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] })
|
||||||
|
if (!features.length) return
|
||||||
|
const clusterId = features[0].properties?.cluster_id
|
||||||
|
const source = map.getSource(sourceId.value) as mapboxgl.GeoJSONSource
|
||||||
|
source.getClusterExpansionZoom(clusterId, (err, zoom) => {
|
||||||
|
if (err) return
|
||||||
|
const geometry = features[0].geometry as GeoJSON.Point
|
||||||
|
map.easeTo({ center: geometry.coordinates as [number, number], zoom: zoom || 4 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('click', 'unclustered-point', (e) => {
|
||||||
|
const features = map.queryRenderedFeatures(e.point, { layers: ['unclustered-point'] })
|
||||||
|
if (!features.length) return
|
||||||
|
emit('select-item', features[0].properties?.uuid)
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('mouseenter', 'clusters', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||||
|
map.on('mouseleave', 'clusters', () => { map.getCanvas().style.cursor = '' })
|
||||||
|
map.on('mouseenter', 'unclustered-point', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||||
|
map.on('mouseleave', 'unclustered-point', () => { map.getCanvas().style.cursor = '' })
|
||||||
|
|
||||||
|
// Auto-fit bounds to all items
|
||||||
|
if (!didFitBounds.value && props.items.length > 0) {
|
||||||
|
const bounds = new LngLatBounds()
|
||||||
|
props.items.forEach(item => {
|
||||||
|
bounds.extend([item.longitude, item.latitude])
|
||||||
|
})
|
||||||
|
map.fitBounds(bounds, { padding: 50, maxZoom: 10 })
|
||||||
|
didFitBounds.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initServerClusteringLayers = (map: MapboxMapType) => {
|
||||||
|
map.addSource(sourceId.value, {
|
||||||
|
type: 'geojson',
|
||||||
|
data: serverClusteredGeoJson.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clusters (count > 1)
|
||||||
|
map.addLayer({
|
||||||
|
id: 'server-clusters',
|
||||||
|
type: 'circle',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['>', ['get', 'count'], 1],
|
||||||
|
paint: {
|
||||||
|
'circle-color': props.pointColor,
|
||||||
|
'circle-radius': ['step', ['get', 'count'], 20, 10, 30, 50, 40],
|
||||||
|
'circle-opacity': 0.8,
|
||||||
|
'circle-stroke-width': 2,
|
||||||
|
'circle-stroke-color': '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'server-cluster-count',
|
||||||
|
type: 'symbol',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['>', ['get', 'count'], 1],
|
||||||
|
layout: {
|
||||||
|
'text-field': ['get', 'count'],
|
||||||
|
'text-size': 14
|
||||||
|
},
|
||||||
|
paint: { 'text-color': '#ffffff' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Individual points (count == 1)
|
||||||
|
map.addLayer({
|
||||||
|
id: 'server-points',
|
||||||
|
type: 'circle',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['==', ['get', 'count'], 1],
|
||||||
|
paint: {
|
||||||
|
'circle-radius': 12,
|
||||||
|
'circle-color': [
|
||||||
|
'case',
|
||||||
|
['==', ['get', 'id'], props.hoveredItemId || ''],
|
||||||
|
'#facc15', // yellow when hovered
|
||||||
|
props.pointColor
|
||||||
|
],
|
||||||
|
'circle-stroke-width': 3,
|
||||||
|
'circle-stroke-color': '#ffffff'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: 'server-point-labels',
|
||||||
|
type: 'symbol',
|
||||||
|
source: sourceId.value,
|
||||||
|
filter: ['==', ['get', 'count'], 1],
|
||||||
|
layout: {
|
||||||
|
'text-field': ['get', 'name'],
|
||||||
|
'text-offset': [0, 1.5],
|
||||||
|
'text-size': 12,
|
||||||
|
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold']
|
||||||
|
},
|
||||||
|
paint: {
|
||||||
|
'text-color': '#ffffff',
|
||||||
|
'text-halo-color': '#000000',
|
||||||
|
'text-halo-width': 1.5
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Click on cluster to zoom in
|
||||||
|
map.on('click', 'server-clusters', (e) => {
|
||||||
|
const features = map.queryRenderedFeatures(e.point, { layers: ['server-clusters'] })
|
||||||
|
if (!features.length) return
|
||||||
|
const expansionZoom = features[0].properties?.expansionZoom
|
||||||
|
const geometry = features[0].geometry as GeoJSON.Point
|
||||||
|
map.easeTo({
|
||||||
|
center: geometry.coordinates as [number, number],
|
||||||
|
zoom: expansionZoom || map.getZoom() + 2
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Click on individual point
|
||||||
|
map.on('click', 'server-points', (e) => {
|
||||||
|
const features = map.queryRenderedFeatures(e.point, { layers: ['server-points'] })
|
||||||
|
if (!features.length) return
|
||||||
|
emit('select-item', features[0].properties?.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('mouseenter', 'server-clusters', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||||
|
map.on('mouseleave', 'server-clusters', () => { map.getCanvas().style.cursor = '' })
|
||||||
|
map.on('mouseenter', 'server-points', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||||
|
map.on('mouseleave', 'server-points', () => { map.getCanvas().style.cursor = '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update map data when items or clusteredPoints change
|
||||||
|
watch(() => props.useServerClustering ? serverClusteredGeoJson.value : geoJsonData.value, (newData) => {
|
||||||
|
if (!mapRef.value || !mapInitialized.value) return
|
||||||
const source = mapRef.value.getSource(sourceId.value) as mapboxgl.GeoJSONSource | undefined
|
const source = mapRef.value.getSource(sourceId.value) as mapboxgl.GeoJSONSource | undefined
|
||||||
if (source) {
|
if (source) {
|
||||||
source.setData(newData)
|
source.setData(newData)
|
||||||
}
|
}
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
// Update hover paint when hoveredItemId changes
|
||||||
|
watch(() => props.hoveredItemId, (newId) => {
|
||||||
|
if (!mapRef.value || !mapInitialized.value) return
|
||||||
|
|
||||||
|
const layerId = props.useServerClustering ? 'server-points' : 'unclustered-point'
|
||||||
|
const propName = props.useServerClustering ? 'id' : 'uuid'
|
||||||
|
|
||||||
|
if (mapRef.value.getLayer(layerId)) {
|
||||||
|
mapRef.value.setPaintProperty(layerId, 'circle-color', [
|
||||||
|
'case',
|
||||||
|
['==', ['get', propName], newId || ''],
|
||||||
|
'#facc15',
|
||||||
|
props.pointColor
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Expose flyTo method for external use (with space fly animation)
|
// Expose flyTo method for external use (with space fly animation)
|
||||||
const flyTo = async (lat: number, lng: number, zoom = 8) => {
|
const flyTo = async (lat: number, lng: number, zoom = 8) => {
|
||||||
if (!mapRef.value) return
|
if (!mapRef.value) return
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
class="block"
|
class="block"
|
||||||
:class="{ 'cursor-pointer': selectable }"
|
:class="{ 'cursor-pointer': selectable }"
|
||||||
@click="selectable && $emit('select')"
|
@click="selectable && $emit('select')"
|
||||||
|
@mouseenter="$emit('hover', true)"
|
||||||
|
@mouseleave="$emit('hover', false)"
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
padding="small"
|
padding="small"
|
||||||
@@ -49,6 +51,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: 'select'): void
|
(e: 'select'): void
|
||||||
|
(e: 'hover', hovered: boolean): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
|
|||||||
@@ -16,6 +16,21 @@ export type Scalars = {
|
|||||||
JSONString: { input: any; output: any; }
|
JSONString: { input: any; output: any; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Cluster or individual point for map display. */
|
||||||
|
export type ClusterPointType = {
|
||||||
|
__typename?: 'ClusterPointType';
|
||||||
|
/** 1 for single point, >1 for cluster */
|
||||||
|
count?: Maybe<Scalars['Int']['output']>;
|
||||||
|
/** Zoom level to expand cluster */
|
||||||
|
expansionZoom?: Maybe<Scalars['Int']['output']>;
|
||||||
|
/** UUID for points, 'cluster-N' for clusters */
|
||||||
|
id?: Maybe<Scalars['String']['output']>;
|
||||||
|
latitude?: Maybe<Scalars['Float']['output']>;
|
||||||
|
longitude?: Maybe<Scalars['Float']['output']>;
|
||||||
|
/** Node name (only for single points) */
|
||||||
|
name?: Maybe<Scalars['String']['output']>;
|
||||||
|
};
|
||||||
|
|
||||||
/** Edge between two nodes (route). */
|
/** Edge between two nodes (route). */
|
||||||
export type EdgeType = {
|
export type EdgeType = {
|
||||||
__typename?: 'EdgeType';
|
__typename?: 'EdgeType';
|
||||||
@@ -67,6 +82,8 @@ export type Query = {
|
|||||||
__typename?: 'Query';
|
__typename?: 'Query';
|
||||||
/** Get auto route between two points via GraphHopper */
|
/** Get auto route between two points via GraphHopper */
|
||||||
autoRoute?: Maybe<RouteType>;
|
autoRoute?: Maybe<RouteType>;
|
||||||
|
/** Get clustered nodes for map display (server-side clustering) */
|
||||||
|
clusteredNodes?: Maybe<Array<Maybe<ClusterPointType>>>;
|
||||||
/** Find routes from product offer nodes to destination */
|
/** Find routes from product offer nodes to destination */
|
||||||
findProductRoutes?: Maybe<Array<Maybe<ProductRouteOptionType>>>;
|
findProductRoutes?: Maybe<Array<Maybe<ProductRouteOptionType>>>;
|
||||||
/** Find K shortest routes through graph between two nodes */
|
/** Find K shortest routes through graph between two nodes */
|
||||||
@@ -97,6 +114,17 @@ export type QueryAutoRouteArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** Root query. */
|
||||||
|
export type QueryClusteredNodesArgs = {
|
||||||
|
east: Scalars['Float']['input'];
|
||||||
|
north: Scalars['Float']['input'];
|
||||||
|
south: Scalars['Float']['input'];
|
||||||
|
transportType?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
west: Scalars['Float']['input'];
|
||||||
|
zoom: Scalars['Int']['input'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/** Root query. */
|
/** Root query. */
|
||||||
export type QueryFindProductRoutesArgs = {
|
export type QueryFindProductRoutesArgs = {
|
||||||
limitRoutes?: InputMaybe<Scalars['Int']['input']>;
|
limitRoutes?: InputMaybe<Scalars['Int']['input']>;
|
||||||
@@ -222,6 +250,18 @@ export type GetAutoRouteQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetAutoRouteQuery = { __typename?: 'Query', autoRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: any | null } | null };
|
export type GetAutoRouteQuery = { __typename?: 'Query', autoRoute?: { __typename?: 'RouteType', distanceKm?: number | null, geometry?: any | null } | null };
|
||||||
|
|
||||||
|
export type GetClusteredNodesQueryVariables = Exact<{
|
||||||
|
west: Scalars['Float']['input'];
|
||||||
|
south: Scalars['Float']['input'];
|
||||||
|
east: Scalars['Float']['input'];
|
||||||
|
north: Scalars['Float']['input'];
|
||||||
|
zoom: Scalars['Int']['input'];
|
||||||
|
transportType?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetClusteredNodesQuery = { __typename?: 'Query', clusteredNodes?: Array<{ __typename?: 'ClusterPointType', id?: string | null, latitude?: number | null, longitude?: number | null, count?: number | null, expansionZoom?: number | null, name?: string | null } | null> | null };
|
||||||
|
|
||||||
export type GetHubCountriesQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetHubCountriesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@@ -267,6 +307,7 @@ export type GetRailRouteQuery = { __typename?: 'Query', railRoute?: { __typename
|
|||||||
export const FindProductRoutesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindProductRoutes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitSources"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitRoutes"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findProductRoutes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"productUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"toUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitSources"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitSources"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitRoutes"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitRoutes"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sourceUuid"}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLat"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"routes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]}}]} as unknown as DocumentNode<FindProductRoutesQuery, FindProductRoutesQueryVariables>;
|
export const FindProductRoutesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindProductRoutes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitSources"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitRoutes"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findProductRoutes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"productUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"toUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitSources"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitSources"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitRoutes"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitRoutes"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sourceUuid"}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLat"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"routes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]}}]} as unknown as DocumentNode<FindProductRoutesQuery, FindProductRoutesQueryVariables>;
|
||||||
export const FindRoutesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindRoutes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findRoutes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"toUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<FindRoutesQuery, FindRoutesQueryVariables>;
|
export const FindRoutesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindRoutes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findRoutes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"toUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<FindRoutesQuery, FindRoutesQueryVariables>;
|
||||||
export const GetAutoRouteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAutoRoute"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"autoRoute"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"}}]}}]}}]} as unknown as DocumentNode<GetAutoRouteQuery, GetAutoRouteQueryVariables>;
|
export const GetAutoRouteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAutoRoute"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"autoRoute"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"}}]}}]}}]} as unknown as DocumentNode<GetAutoRouteQuery, GetAutoRouteQueryVariables>;
|
||||||
|
export const GetClusteredNodesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetClusteredNodes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"west"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"south"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"east"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"north"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clusteredNodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"west"},"value":{"kind":"Variable","name":{"kind":"Name","value":"west"}}},{"kind":"Argument","name":{"kind":"Name","value":"south"},"value":{"kind":"Variable","name":{"kind":"Name","value":"south"}}},{"kind":"Argument","name":{"kind":"Name","value":"east"},"value":{"kind":"Variable","name":{"kind":"Name","value":"east"}}},{"kind":"Argument","name":{"kind":"Name","value":"north"},"value":{"kind":"Variable","name":{"kind":"Name","value":"north"}}},{"kind":"Argument","name":{"kind":"Name","value":"zoom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}}},{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"expansionZoom"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<GetClusteredNodesQuery, GetClusteredNodesQueryVariables>;
|
||||||
export const GetHubCountriesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetHubCountries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hubCountries"}}]}}]} as unknown as DocumentNode<GetHubCountriesQuery, GetHubCountriesQueryVariables>;
|
export const GetHubCountriesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetHubCountries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hubCountries"}}]}}]} as unknown as DocumentNode<GetHubCountriesQuery, GetHubCountriesQueryVariables>;
|
||||||
export const GetNodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<GetNodeQuery, GetNodeQueryVariables>;
|
export const GetNodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<GetNodeQuery, GetNodeQueryVariables>;
|
||||||
export const GetNodeConnectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNodeConnections"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodeConnections"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitAuto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitRail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hub"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railNode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<GetNodeConnectionsQuery, GetNodeConnectionsQueryVariables>;
|
export const GetNodeConnectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNodeConnections"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodeConnections"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitAuto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitRail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hub"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railNode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode<GetNodeConnectionsQuery, GetNodeConnectionsQueryVariables>;
|
||||||
|
|||||||
48
app/composables/useClusteredNodes.ts
Normal file
48
app/composables/useClusteredNodes.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { GetClusteredNodesDocument } from './graphql/public/geo-generated'
|
||||||
|
import type { ClusterPointType } from './graphql/public/geo-generated'
|
||||||
|
|
||||||
|
export interface MapBounds {
|
||||||
|
west: number
|
||||||
|
south: number
|
||||||
|
east: number
|
||||||
|
north: number
|
||||||
|
zoom: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useClusteredNodes(transportType?: Ref<string | undefined>) {
|
||||||
|
const { $geoClient } = useNuxtApp()
|
||||||
|
|
||||||
|
const clusteredNodes = ref<ClusterPointType[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const fetchClusters = async (bounds: MapBounds) => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await $geoClient.query({
|
||||||
|
query: GetClusteredNodesDocument,
|
||||||
|
variables: {
|
||||||
|
west: bounds.west,
|
||||||
|
south: bounds.south,
|
||||||
|
east: bounds.east,
|
||||||
|
north: bounds.north,
|
||||||
|
zoom: Math.floor(bounds.zoom),
|
||||||
|
transportType: transportType?.value
|
||||||
|
},
|
||||||
|
fetchPolicy: 'network-only'
|
||||||
|
})
|
||||||
|
|
||||||
|
clusteredNodes.value = (data?.clusteredNodes ?? []).filter(Boolean) as ClusterPointType[]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch clustered nodes:', error)
|
||||||
|
clusteredNodes.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clusteredNodes,
|
||||||
|
loading,
|
||||||
|
fetchClusters
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
selectable
|
selectable
|
||||||
:is-selected="selectedItemId === hub.uuid"
|
:is-selected="selectedItemId === hub.uuid"
|
||||||
@select="selectItem(hub)"
|
@select="selectItem(hub)"
|
||||||
|
@hover="(hovered) => onHubHover(hub.uuid, hovered)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</CatalogMapSidebar>
|
</CatalogMapSidebar>
|
||||||
@@ -28,14 +29,19 @@
|
|||||||
<CatalogMap
|
<CatalogMap
|
||||||
ref="mapRef"
|
ref="mapRef"
|
||||||
map-id="hubs-fullscreen-map"
|
map-id="hubs-fullscreen-map"
|
||||||
:items="itemsWithCoords"
|
:clustered-points="clusteredNodes"
|
||||||
|
use-server-clustering
|
||||||
|
:hovered-item-id="hoveredItemId"
|
||||||
point-color="#10b981"
|
point-color="#10b981"
|
||||||
@select-item="onMapSelectItem"
|
@select-item="onMapSelectItem"
|
||||||
|
@bounds-change="onBoundsChange"
|
||||||
/>
|
/>
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: false
|
layout: false
|
||||||
})
|
})
|
||||||
@@ -48,14 +54,16 @@ const {
|
|||||||
selectedFilter,
|
selectedFilter,
|
||||||
filters,
|
filters,
|
||||||
isLoading,
|
isLoading,
|
||||||
itemsWithCoords,
|
|
||||||
init
|
init
|
||||||
} = useCatalogHubs()
|
} = useCatalogHubs()
|
||||||
|
|
||||||
await init()
|
await init()
|
||||||
|
|
||||||
|
const { clusteredNodes, fetchClusters } = useClusteredNodes()
|
||||||
|
|
||||||
const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null)
|
const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null)
|
||||||
const selectedItemId = ref<string | null>(null)
|
const selectedItemId = ref<string | null>(null)
|
||||||
|
const hoveredItemId = ref<string | null>(null)
|
||||||
|
|
||||||
const selectItem = (item: any) => {
|
const selectItem = (item: any) => {
|
||||||
selectedItemId.value = item.uuid
|
selectedItemId.value = item.uuid
|
||||||
@@ -67,4 +75,12 @@ const selectItem = (item: any) => {
|
|||||||
const onMapSelectItem = (uuid: string) => {
|
const onMapSelectItem = (uuid: string) => {
|
||||||
selectedItemId.value = uuid
|
selectedItemId.value = uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onHubHover = (uuid: string | undefined | null, hovered: boolean) => {
|
||||||
|
hoveredItemId.value = hovered && uuid ? uuid : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBoundsChange = (bounds: MapBounds) => {
|
||||||
|
fetchClusters(bounds)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
24
graphql/operations/public/geo/GetClusteredNodes.graphql
Normal file
24
graphql/operations/public/geo/GetClusteredNodes.graphql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
query GetClusteredNodes(
|
||||||
|
$west: Float!
|
||||||
|
$south: Float!
|
||||||
|
$east: Float!
|
||||||
|
$north: Float!
|
||||||
|
$zoom: Int!
|
||||||
|
$transportType: String
|
||||||
|
) {
|
||||||
|
clusteredNodes(
|
||||||
|
west: $west
|
||||||
|
south: $south
|
||||||
|
east: $east
|
||||||
|
north: $north
|
||||||
|
zoom: $zoom
|
||||||
|
transportType: $transportType
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
count
|
||||||
|
expansionZoom
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user