Skip to content

Commit 691e59d

Browse files
committed
feat: you can now search "around" a saved route
1 parent 10fe54a commit 691e59d

File tree

7 files changed

+216
-180
lines changed

7 files changed

+216
-180
lines changed

app/components/common/IconButton.svelte

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
canvas?.nativeView?.redraw();
6363
}
6464
function onCanvasDraw({ canvas, object }: { canvas: Canvas; object: CanvasView }) {
65+
if (!text) {
66+
return;
67+
}
6568
const theFontFamily = fontFamily || $fonts.mdi;
6669
let iconPaint = iconPaints[theFontFamily];
6770
if (!iconPaint) {

app/components/items/ItemEdit.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { HorizontalPosition, VerticalPosition } from '@nativescript-community/ui-popover';
88
import { showPopover } from '@nativescript-community/ui-popover/svelte';
99
import { Color, File, ObservableArray, Utils, View } from '@nativescript/core';
10-
import type { Point as GeoJSONPoint, Point } from 'geojson';
10+
import type { Point as GeoJSONPoint } from 'geojson';
1111
import { Template } from 'svelte-native/components';
1212
import { NativeViewElementNode } from 'svelte-native/dom';
1313
import { GeoLocation } from '~/handlers/GeoHandler';
@@ -262,7 +262,7 @@
262262
async function featchAddress(listItem, event) {
263263
try {
264264
const SearchModal = (await import('~/components/search/SearchModal.svelte')).default;
265-
const geometry = item.geometry as Point;
265+
const geometry = item.geometry as GeoJSONPoint;
266266
const position = { lat: geometry.coordinates[1], lon: geometry.coordinates[0], altitude: geometry.coordinates[2] } as GeoLocation;
267267
// const result: any = await showModal({ page: Settings, fullscreen: true, props: { position } });
268268
const anchorView = event.object as View;

app/components/map/Map.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,7 @@
22442244
style="z-index:1000;"
22452245
defaultElevation={0}
22462246
isUserInteractionEnabled={scrollingWidgetsOpacity > 0.3}
2247+
item={$selectedItem}
22472248
margin={10}
22482249
verticalAlignment="top"
22492250
android:marginTop={windowInsetTop + 10} />

app/components/search/Search.svelte

+11-7
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
let _searchLayer: VectorTileLayer;
6363
let searchAsTypeTimer;
6464
let loading = false;
65+
let searchAroundItem = false;
6566
// let filteringOSMKey = false;
6667
export let dataItems: ObservableArray<SearchItem> = null;
68+
export let item: Item;
6769
// let text: string = null;
6870
let currentSearchText: string = null;
6971
const mapContext = getMapContext();
@@ -239,7 +241,7 @@
239241
await loadView();
240242
}
241243
needToShowOnResult = true;
242-
await collectionView?.instantSearch(_query);
244+
await collectionView?.instantSearch(_query, undefined, searchAroundItem ? item : undefined);
243245
didSearch = true;
244246
} catch (err) {
245247
needToShowOnResult = false;
@@ -313,11 +315,12 @@
313315
function showResultsOnMap(items: ObservableArray<SearchItem>, shouldUnfocus = true) {
314316
try {
315317
if (!items || items.length === 0) {
316-
_searchDataSource.deleteLayer(1);
317-
_searchDataSource.createLayer('search');
318+
_searchDataSource?.deleteLayer(1);
319+
_searchDataSource?.createLayer('search');
318320
mapContext.showMapResultsPager(null);
319321
showingOnMap = false;
320322
} else {
323+
getSearchLayer().visible = true;
321324
showingOnMap = true;
322325
// if (!_searchDataSource) {
323326
const dataSource = getSearchDataSource();
@@ -328,13 +331,13 @@
328331
// if (!searchStyle) {
329332
// searchStyle = new PointStyleBuilder({ color: 'red', size: 10 });
330333
// }
331-
const featureCollection = packageService.getGeoJSONReader().readFeatureCollection(geojson);
332334
// dataSource.clear();
333335
dataSource.setLayerGeoJSONString(1, geojson);
334336
// items.forEach((d) => {
335337
// dataSource.add(createSearchMarker(d));
336338
// });
337339
ensureSearchLayer();
340+
const featureCollection = packageService.getGeoJSONReader().readFeatureCollection(geojson);
338341
const mapBounds = featureCollection.getBounds();
339342
340343
const viewPort = mapContext.getMapViewPort();
@@ -443,7 +446,7 @@
443446
id="search"
444447
{...$$restProps}
445448
backgroundColor={colorWidgetBackground}
446-
columns="auto,*,auto,auto,auto"
449+
columns="auto,*,auto,auto,auto,auto"
447450
elevation={$currentTheme !== 'dark' && focused ? 6 : 0}
448451
rows="auto,auto"
449452
on:tap={() => {}}>
@@ -468,8 +471,9 @@
468471
on:focus={onFocus} />
469472
<activityindicator busy={true} col={2} height={20} visibility={loading ? 'visible' : 'hidden'} width={20} />
470473
<IconButton col={2} gray={true} isVisible={currentSearchText && currentSearchText.length > 0 && !loading && didSearch} text="mdi-refresh" on:tap={reloadSearch} />
471-
<IconButton col={3} gray={true} isVisible={currentSearchText && currentSearchText.length > 0} text="mdi-close" on:tap={() => clearSearch()} />
472-
<IconButton accessibilityValue="menuBtn" col={4} gray={true} onLongPress={showMapOptions} text="mdi-dots-vertical" on:tap={showMapMenu} />
474+
<IconButton col={3} isSelected={searchAroundItem} isVisible={focused && !!item} text="mdi-map-marker-path" on:tap={() => (searchAroundItem = !searchAroundItem)} />
475+
<IconButton col={4} gray={true} isVisible={currentSearchText && currentSearchText.length > 0} text="mdi-close" on:tap={() => clearSearch()} />
476+
<IconButton accessibilityValue="menuBtn" col={5} gray={true} onLongPress={showMapOptions} text="mdi-dots-vertical" on:tap={showMapMenu} />
473477
{#if loaded}
474478
<absolutelayout bind:this={collectionViewHolder} id="searchCollectionViewHolder" clipToBounds={true} colSpan={7} height={0} isUserInteractionEnabled={searchResultsVisible} row={1}>
475479
<gridlayout id="searchCollectionViewSubHolder" columns="auto,auto,*" height={SEARCH_COLLECTIONVIEW_HEIGHT} rows="*,auto" width="100%">

app/components/search/SearchCollectionView.svelte

+52-19
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<script lang="ts">
22
import { l } from '@nativescript-community/l';
3-
import type { MapBounds, MapPos } from '@nativescript-community/ui-carto/core';
3+
import { GenericMapPos, MapBounds, MapPos } from '@nativescript-community/ui-carto/core';
4+
import { LineGeometry } from '@nativescript-community/ui-carto/geometry';
45
import { SearchRequest, VectorTileSearchServiceOptions } from '@nativescript-community/ui-carto/search';
5-
import { showSnack } from '~/utils/ui';
6+
import { CollectionView } from '@nativescript-community/ui-collectionview';
67
import { ApplicationSettings, ObservableArray, Screen } from '@nativescript/core';
7-
import deburr from 'deburr';
8-
import type { Point } from 'geojson';
98
import { createEventDispatcher } from '@shared/utils/svelte/ui';
9+
import deburr from 'deburr';
10+
import type { Point as GeoJSONPoint } from 'geojson';
1011
import { Template } from 'svelte-native/components';
12+
import { NativeViewElementNode } from 'svelte-native/dom';
13+
import { GeoLocation } from '~/handlers/GeoHandler';
1114
import { formatDistance, osmicon } from '~/helpers/formatter';
12-
import { getBoundsOfDistance, getMetersPerPixel } from '~/helpers/geolib';
15+
import { getBoundsOfDistance, getDistance, getDistanceSimple, getMetersPerPixel } from '~/helpers/geolib';
1316
import { lc } from '~/helpers/locale';
17+
import { onThemeChanged } from '~/helpers/theme';
1418
import { formatter } from '~/mapModules/ItemFormatter';
1519
import { getMapContext } from '~/mapModules/MapModule';
1620
import type { IItem as Item } from '~/models/Item';
@@ -19,13 +23,10 @@
1923
import type { GeoResult } from '~/services/PackageService';
2024
import { packageService } from '~/services/PackageService';
2125
import { computeDistanceBetween } from '~/utils/geo';
26+
import { showSnack } from '~/utils/ui';
2227
import { arraySortOn } from '~/utils/utils';
2328
import { colors } from '~/variables';
2429
import { HereFeature, PhotonFeature } from './Features';
25-
import { onThemeChanged } from '~/helpers/theme';
26-
import { NativeViewElementNode } from 'svelte-native/dom';
27-
import { CollectionView } from '@nativescript-community/ui-collectionview';
28-
import { LineGeometry } from '@nativescript-community/ui-carto/geometry';
2930
3031
$: ({ colorOnSurface, colorOnSurfaceVariant } = $colors);
3132
@@ -38,7 +39,6 @@
3839
export let searchResultsCount = 0;
3940
const mapContext = getMapContext();
4041
41-
4242
// export function getFilteredDataItems() {
4343
// return filteredDataItems;
4444
// }
@@ -116,11 +116,10 @@
116116
'distance'
117117
) as GeoResult[];
118118
}
119-
async function searchInVectorTiles(enabled: boolean, options: SearchRequest & VectorTileSearchServiceOptions) {
119+
async function searchInVectorTiles(enabled: boolean, options: SearchRequest & VectorTileSearchServiceOptions & { bounds?: MapBounds }) {
120120
if (!enabled) {
121121
return [];
122122
}
123-
DEV_LOG && console.log('searchInVectorTiles', JSON.stringify(options));
124123
const result = await packageService.searchInVectorTiles(options);
125124
if (result) {
126125
return packageService.convertFeatureCollection(result, options);
@@ -202,13 +201,46 @@
202201
'distance'
203202
);
204203
}
205-
export async function instantSearch(_query, position = mapContext.getMap().focusPos) {
204+
export async function instantSearch(_query, position?: GenericMapPos<LatLonKeys>, item?: Item) {
206205
try {
207206
loading = true;
208207
currentQuery = cleanUpString(_query);
209-
const mpp = getMetersPerPixel(position, mapContext.getMap().getZoom());
210-
const searchRadius = Math.min(Math.max(mpp * Screen.mainScreen.widthPixels * 2, mpp * Screen.mainScreen.heightPixels * 2), 50000); //meters;
208+
let bounds: MapBounds<LatLonKeys>;
209+
if (!position) {
210+
if (item) {
211+
if (item.properties?.zoomBounds) {
212+
bounds = item.properties.zoomBounds;
213+
} else if (item.properties?.extent) {
214+
let extent: [number, number, number, number] = item.properties.extent as [number, number, number, number];
215+
if (typeof extent === 'string') {
216+
if (extent[0] !== '[') {
217+
extent = `[${extent as string}]` as any;
218+
}
219+
extent = JSON.parse(extent as any);
220+
}
221+
bounds = new MapBounds({ lat: extent[1], lon: extent[0] }, { lat: extent[3], lon: extent[2] });
222+
} else if (item.route) {
223+
const geometry = packageService.getRouteItemGeometry(item);
224+
//we need to convert geometry bounds to wgs84
225+
//not perfect as vectorTile geometry might not represent the whole entier route at higher zoom levels
226+
bounds = geometry?.getBounds();
227+
} else {
228+
const geometry = item.geometry as GeoJSONPoint;
229+
position = { lat: geometry.coordinates[1], lon: geometry.coordinates[0] };
230+
}
231+
if (bounds && !position) {
232+
position = { lat: bounds.southwest.lat + (bounds.northeast.lat - bounds.southwest.lat) / 2, lon: bounds.southwest.lon + (bounds.northeast.lon - bounds.southwest.lon) / 2 };
233+
}
234+
} else {
235+
position = mapContext.getMap().focusPos;
236+
}
237+
}
211238
239+
const mpp = getMetersPerPixel(position, mapContext.getMap().getZoom());
240+
const searchRadius = bounds
241+
? Math.max(getDistanceSimple(position, { lat: bounds.southwest.lat, lon: position.lon }), getDistanceSimple(position, { lon: bounds.southwest.lon, lat: position.lat })) + 1000
242+
: Math.min(Math.max(mpp * Screen.mainScreen.widthPixels * 2, mpp * Screen.mainScreen.heightPixels * 2), 50000); //meters;
243+
DEV_LOG && console.log('instantSearch', currentQuery, !!item, position, bounds, searchRadius);
212244
const options = {
213245
query: currentQuery,
214246
projection: mapContext.getProjection(),
@@ -222,10 +254,10 @@
222254
position,
223255
locationRadius: searchRadius,
224256
searchRadius,
225-
bounds: getBoundsOfDistance(position, searchRadius)
257+
bounds: bounds || getBoundsOfDistance(position, searchRadius) // only for Photon
226258
// locationRadius: 1000,
227259
};
228-
DEV_LOG && console.log('instantSearch', JSON.stringify(position), mapContext.getMap().getZoom(), mpp, JSON.stringify(options));
260+
// DEV_LOG && console.log('instantSearch', JSON.stringify(position), mapContext.getMap().getZoom(), mpp, JSON.stringify(options));
229261
let newDataItems = new ObservableArray<SearchItem>();
230262
231263
function addItems(items: SearchItem[]) {
@@ -246,7 +278,7 @@
246278
// updateFilteredDataItems(filteringOSMKey);
247279
}
248280
if (/^(class|subclass)/.test(currentQuery)) {
249-
const bounds = mapContext.getMap().getMapBounds();
281+
bounds = bounds || mapContext.getMap().getMapBounds();
250282
DEV_LOG && console.log('bounds', bounds);
251283
252284
const zoom = /peak|campsite/.test(currentQuery) ? 11 : 14;
@@ -257,6 +289,7 @@
257289
await searchInVectorTiles(true, {
258290
projection: options.projection,
259291
geometry,
292+
bounds,
260293
minZoom: zoom,
261294
maxZoom: zoom,
262295
filterExpression: `regexp_ilike(${array[0]},'.*${array[1]}.*')`
@@ -298,7 +331,7 @@
298331
}
299332
}
300333
export function clearSearch(clearQuery = true) {
301-
DEV_LOG && console.log('clearSearch', clearQuery, new Error().stack);
334+
// DEV_LOG && console.log('clearSearch', clearQuery, new Error().stack);
302335
networkService.clearRequests('photon', 'here');
303336
loading = false;
304337
dataItems = new ObservableArray();

0 commit comments

Comments
 (0)