Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Location Search #5

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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