Skip to content

Commit

Permalink
Merge pull request #5 from SERVIR/map/locationsearch
Browse files Browse the repository at this point in the history
Location Search
  • Loading branch information
billyz313 authored Oct 30, 2024
2 parents 608a2f4 + 4b7d753 commit e3e1dd2
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 1 deletion.
4 changes: 4 additions & 0 deletions playground/src/components/layout/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ const components = [
title: 'SMapControl',
link: '/components/s-map-control',
},
{
title: 'SLocationSearch',
link: '/components/s-location-search',
},
]
</script>

Expand Down
89 changes: 89 additions & 0 deletions playground/src/pages/Components/s-location-search.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<component-doc title="SMapControl">
<template #description>
SMapControl consists of control such as zoom and home button.
</template>
<template #example>
<l-map
ref="lmap"
style="min-height: 500px"
:zoom="zoom"
:center="center"
:options="{ zoomControl: false }"
>
<l-control position="topleft">
<s-location-search :lmap="lmap" />
</l-control>

<LTileLayer
url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"
attribution='&amp;copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
layer-type="base"
name="OpenStreetMap"
/>
</l-map>
<div class="pt-4" />
</template>
<template #prop>
<v-table>
<thead class="bg-floor">
<tr>
<th>Props</th>
<th>Type</th>
<th>Defaults</th>
<th>Comment</th>
</tr>
</thead>
<tbody>

<tr>
<td>size</td>
<td>string</td>
<td>default</td>
<td>
size options are: x-small, small, default, large,
and x-large.
</td>
</tr>
<tr>
<td>rounded</td>
<td>string</td>
<td>true</td>
<td>
rounded options are: 0, xs, sm, true, lg, xl, pill,
circle, and shaped.
</td>
</tr>


<tr>
<td>lmap</td>
<td>any</td>
<td />
<td>Required. Leaflet map reference.</td>
</tr>
<tr>
<td>variant</td>
<td>string</td>
<td>outlined</td>
<td>
variant options are: 'flat' | 'text' | 'elevated' |
'tonal' | 'outlined' | 'plain'
</td>
</tr>
</tbody>
</v-table>
</template>
</component-doc>
</template>

<script setup lang="ts">
import { LMap, LTileLayer, LControl } from '@vue-leaflet/vue-leaflet'
import type { PointExpression } from 'leaflet'
import { ref } from 'vue'
import ComponentDoc from '~/components/layout/ComponentDoc.vue'
const lmap = ref<any>(null)
const zoom = ref(15)
const center = ref([47.21322, -1.559482] as PointExpression)
</script>
143 changes: 143 additions & 0 deletions src/runtime/components/SLocationSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<template>
<div class="s-location-search search-container">
<v-btn
:variant="props.variant"
:size="props.size"
:rounded="props.rounded"
color="primary"
@click="toggleSearch"
>
<v-icon>mdi-map-search</v-icon>
</v-btn>

<div v-if="showSearch" class="input-wrapper">
<v-text-field
v-model="searchQuery"
label="Enter address"
variant="solo"
color="primary"
density="compact"
clearable
hide-details
dense
class="search-input"
@keyup.enter="fetchLocations"
/>

<v-list
v-if="locations.length"
class="search-results"
density="compact"
>
<v-list-item
v-for="(location, index) in locations"
:key="index"
class="pa-0"
color="primary"
@click="zoomToLocation(location)"
>
<template #prepend>
<div class="pa-2">
<v-icon class="">mdi-map-marker-radius</v-icon>
</div>
</template>
<v-list-item-title class="text-no-wrap">{{
location.display_name
}}</v-list-item-title>
</v-list-item>
</v-list>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface Props {
lmap: any
size?: string
rounded?: string
variant: 'outlined' | 'flat' | 'text' | 'elevated' | 'tonal' | 'plain'
}
const props = withDefaults(defineProps<Props>(), {
size: 'default',
rounded: 'true',
variant: 'outlined',
})
const searchQuery = ref<string>('')
const locations = ref<Array<{ display_name: string }>>([])
const showSearch = ref<boolean>(false)
const toggleSearch = () => {
showSearch.value = !showSearch.value
if (!showSearch.value) {
searchQuery.value = '' // Clear the search query when hiding
locations.value = [] // Clear the results when hiding
}
}
const fetchLocations = async () => {
if (!searchQuery.value) return
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(searchQuery.value)}&format=json&limit=5`
)
if (!response.ok) throw new Error('Failed to fetch locations')
const data = await response.json()
locations.value = data
} catch (error) {
console.error('Error fetching locations:', error)
}
}
const zoomToLocation = (location: any) => {
if (!props.lmap) return
console.log(
'zoom to location',
location?.boundingbox,
props.lmap.leafletObject.getBounds()
)
try {
const bounds = [
[
parseFloat(location.boundingbox[0]),
parseFloat(location.boundingbox[2]),
],
[
parseFloat(location.boundingbox[1]),
parseFloat(location.boundingbox[3]),
],
]
props.lmap.leafletObject.flyToBounds(bounds, {
duration: 1.75,
easeLinearity: 0.25,
})
} catch (error) {
console.error('Invalid bounding box:', error)
}
}
</script>

<style scoped>
.search-container {
display: flex;
align-items: center;
position: relative;
}
.input-wrapper {
position: relative;
margin-left: 8px;
}
.search-input {
width: 400px;
}
.search-results {
position: absolute;
min-width: 400px;
}
</style>
2 changes: 1 addition & 1 deletion src/runtime/components/SMapControl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface Props {
zoom?: number
center: PointExpression
lmap: any
variant: string
variant: 'outlined' | 'flat' | 'text' | 'elevated' | 'tonal' | 'plain'
}
const props = withDefaults(defineProps<Props>(), {
flex: 'column',
Expand Down

0 comments on commit e3e1dd2

Please sign in to comment.