|
@@ -11,7 +11,7 @@ import {
|
|
|
ActivityIndicator,
|
|
ActivityIndicator,
|
|
|
ScrollView
|
|
ScrollView
|
|
|
} from 'react-native';
|
|
} from 'react-native';
|
|
|
-import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
|
|
|
|
|
+import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
|
|
|
|
|
|
|
import * as MapLibreRN from '@maplibre/maplibre-react-native';
|
|
import * as MapLibreRN from '@maplibre/maplibre-react-native';
|
|
|
import { styles } from './style';
|
|
import { styles } from './style';
|
|
@@ -760,6 +760,27 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
);
|
|
);
|
|
|
}, [nomadsFilter, loading]);
|
|
}, [nomadsFilter, loading]);
|
|
|
|
|
|
|
|
|
|
+ const isMustModeEnabled = useMemo(
|
|
|
|
|
+ () => seriesFilter.visible && seriesFilter.groups?.includes(-1),
|
|
|
|
|
+ [seriesFilter]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const isGroupEnabled = useCallback(
|
|
|
|
|
+ (groupId: number) => {
|
|
|
|
|
+ if (!seriesFilter.visible) return false;
|
|
|
|
|
+ if (!seriesFilter.groups?.length) return true;
|
|
|
|
|
+ return seriesFilter.groups.includes(groupId);
|
|
|
|
|
+ },
|
|
|
|
|
+ [seriesFilter]
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const seriesGroups = useMemo(() => {
|
|
|
|
|
+ if (!seriesIcons?.data) return [];
|
|
|
|
|
+ return seriesIcons.data.map((icon) => ({
|
|
|
|
|
+ id: icon.id
|
|
|
|
|
+ }));
|
|
|
|
|
+ }, [seriesIcons]);
|
|
|
|
|
+
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!seriesFilter.visible) {
|
|
if (!seriesFilter.visible) {
|
|
|
setSeriesVisitedFilter(generateFilter([]));
|
|
setSeriesVisitedFilter(generateFilter([]));
|
|
@@ -1229,6 +1250,26 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
|
|
|
|
|
const onMapPress = async (event: any) => {
|
|
const onMapPress = async (event: any) => {
|
|
|
if (!mapRef.current) return;
|
|
if (!mapRef.current) return;
|
|
|
|
|
+
|
|
|
|
|
+ const { screenPointX, screenPointY } = event.properties;
|
|
|
|
|
+ const seriesLayerIds = seriesGroups.flatMap((g) => [
|
|
|
|
|
+ `series-${g.id}-unvisited`,
|
|
|
|
|
+ `series-${g.id}-must-icon`,
|
|
|
|
|
+ `series-${g.id}-visited-normal`,
|
|
|
|
|
+ `series-${g.id}-visited-must`
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ const { features: seriesFeatures } = await mapRef.current.queryRenderedFeaturesAtPoint(
|
|
|
|
|
+ [screenPointX, screenPointY],
|
|
|
|
|
+ undefined,
|
|
|
|
|
+ seriesLayerIds
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (seriesFeatures?.length) {
|
|
|
|
|
+ handleMarkerPress({ features: seriesFeatures });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (selectedMarker || selectedUser) {
|
|
if (selectedMarker || selectedUser) {
|
|
|
closeCallout();
|
|
closeCallout();
|
|
|
return;
|
|
return;
|
|
@@ -1742,7 +1783,8 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
<MapLibreRN.MapView
|
|
<MapLibreRN.MapView
|
|
|
ref={mapRef}
|
|
ref={mapRef}
|
|
|
style={styles.map}
|
|
style={styles.map}
|
|
|
- mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps2025.json'}
|
|
|
|
|
|
|
+ // mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps2025.json'}
|
|
|
|
|
+ mapStyle={VECTOR_MAP_HOST + '/nomadmania-maps2026.json'}
|
|
|
rotateEnabled={false}
|
|
rotateEnabled={false}
|
|
|
attributionEnabled={false}
|
|
attributionEnabled={false}
|
|
|
onPress={onMapPress}
|
|
onPress={onMapPress}
|
|
@@ -1922,7 +1964,209 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
</>
|
|
</>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- <MapLibreRN.VectorSource
|
|
|
|
|
|
|
+ {didFinishLoadingStyle &&
|
|
|
|
|
+ seriesGroups?.length > 0 &&
|
|
|
|
|
+ seriesGroups.map((group) => {
|
|
|
|
|
+ const visible = isGroupEnabled(group.id);
|
|
|
|
|
+ const mustVisible = seriesFilter.visible && (visible || isMustModeEnabled);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <React.Fragment key={`series-group-${group.id}`}>
|
|
|
|
|
+ {seriesFilter.status !== 1
|
|
|
|
|
+ ? (() => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return [
|
|
|
|
|
+ <MapLibreRN.SymbolLayer
|
|
|
|
|
+ key={`symbol_unvisited_normal_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-unvisited`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_layer['source-layer']}
|
|
|
|
|
+ aboveLayerID={'waterway-name'}
|
|
|
|
|
+ filter={['all', seriesNotVisitedFilter, ['!=', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ symbolSpacing: 1,
|
|
|
|
|
+ iconImage: '{series_id}',
|
|
|
|
|
+ iconAllowOverlap: true,
|
|
|
|
|
+ iconIgnorePlacement: true,
|
|
|
|
|
+ visibility: visible ? 'visible' : 'none',
|
|
|
|
|
+ iconColor: '#666',
|
|
|
|
|
+ iconOpacity: 1,
|
|
|
|
|
+ iconHaloColor: '#ffffff',
|
|
|
|
|
+ iconHaloWidth: 1,
|
|
|
|
|
+ iconHaloBlur: 0.5,
|
|
|
|
|
+ textAnchor: 'top',
|
|
|
|
|
+ textField: '{name}',
|
|
|
|
|
+ textFont: ['Noto Sans Regular'],
|
|
|
|
|
+ textMaxWidth: 9,
|
|
|
|
|
+ textOffset: [0, 1.3],
|
|
|
|
|
+ textPadding: 2,
|
|
|
|
|
+ textSize: 12,
|
|
|
|
|
+ textOptional: true,
|
|
|
|
|
+ textIgnorePlacement: false,
|
|
|
|
|
+ textAllowOverlap: false,
|
|
|
|
|
+ textColor: '#666',
|
|
|
|
|
+ textHaloColor: '#ffffff',
|
|
|
|
|
+ textHaloWidth: 1,
|
|
|
|
|
+ textHaloBlur: 0.5
|
|
|
|
|
+ }}
|
|
|
|
|
+ />,
|
|
|
|
|
+ <MapLibreRN.CircleLayer
|
|
|
|
|
+ key={`circle_unvisited_must_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-must-wrapper`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_layer['source-layer']}
|
|
|
|
|
+ aboveLayerID={`series-${group.id}-unvisited`}
|
|
|
|
|
+ filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ circleRadius: 16,
|
|
|
|
|
+ circleColor: Colors.ORANGE,
|
|
|
|
|
+ circleOpacity: 1,
|
|
|
|
|
+ circleStrokeWidth: 2.5,
|
|
|
|
|
+ circleStrokeColor: Colors.ORANGE,
|
|
|
|
|
+ visibility: mustVisible ? 'visible' : 'none'
|
|
|
|
|
+ }}
|
|
|
|
|
+ />,
|
|
|
|
|
+ <MapLibreRN.SymbolLayer
|
|
|
|
|
+ key={`symbol_unvisited_must_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-must-icon`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_layer['source-layer']}
|
|
|
|
|
+ aboveLayerID={`series-${group.id}-must-wrapper`}
|
|
|
|
|
+ filter={['all', seriesNotVisitedFilter, ['==', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ symbolSpacing: 1,
|
|
|
|
|
+ iconImage: '{series_id}',
|
|
|
|
|
+ iconAllowOverlap: true,
|
|
|
|
|
+ iconIgnorePlacement: true,
|
|
|
|
|
+ visibility: mustVisible ? 'visible' : 'none',
|
|
|
|
|
+ iconColor: '#666',
|
|
|
|
|
+ iconOpacity: 1,
|
|
|
|
|
+ iconHaloColor: '#ffffff',
|
|
|
|
|
+ iconHaloWidth: 1,
|
|
|
|
|
+ iconHaloBlur: 0.5,
|
|
|
|
|
+ textAnchor: 'top',
|
|
|
|
|
+ textField: '{name}',
|
|
|
|
|
+ textFont: ['Noto Sans Regular'],
|
|
|
|
|
+ textMaxWidth: 9,
|
|
|
|
|
+ textOffset: [0, 1.3],
|
|
|
|
|
+ textPadding: 2,
|
|
|
|
|
+ textSize: 12,
|
|
|
|
|
+ textOptional: true,
|
|
|
|
|
+ textIgnorePlacement: false,
|
|
|
|
|
+ textAllowOverlap: false,
|
|
|
|
|
+ textColor: '#666',
|
|
|
|
|
+ textHaloColor: '#ffffff',
|
|
|
|
|
+ textHaloWidth: 1,
|
|
|
|
|
+ textHaloBlur: 0.5
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('SymbolLayer render error:', error);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ })()
|
|
|
|
|
+ : null}
|
|
|
|
|
+
|
|
|
|
|
+ {seriesFilter.status !== 0
|
|
|
|
|
+ ? (() => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return [
|
|
|
|
|
+ <MapLibreRN.SymbolLayer
|
|
|
|
|
+ key={`symbol_visited_normal_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-visited-normal`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_visited['source-layer']}
|
|
|
|
|
+ aboveLayerID={'waterway-name'}
|
|
|
|
|
+ filter={['all', seriesVisitedFilter, ['!=', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ symbolSpacing: 1,
|
|
|
|
|
+ iconImage: '{series_id}v',
|
|
|
|
|
+ iconAllowOverlap: true,
|
|
|
|
|
+ iconIgnorePlacement: true,
|
|
|
|
|
+ visibility: visible ? 'visible' : 'none',
|
|
|
|
|
+ iconColor: '#666',
|
|
|
|
|
+ iconOpacity: 1,
|
|
|
|
|
+ iconHaloColor: '#ffffff',
|
|
|
|
|
+ iconHaloWidth: 1,
|
|
|
|
|
+ iconHaloBlur: 0.5,
|
|
|
|
|
+ textAnchor: 'top',
|
|
|
|
|
+ textField: '{name}',
|
|
|
|
|
+ textFont: ['Noto Sans Regular'],
|
|
|
|
|
+ textMaxWidth: 9,
|
|
|
|
|
+ textOffset: [0, 1.3],
|
|
|
|
|
+ textPadding: 2,
|
|
|
|
|
+ textSize: 12,
|
|
|
|
|
+ textOptional: true,
|
|
|
|
|
+ textIgnorePlacement: false,
|
|
|
|
|
+ textAllowOverlap: false,
|
|
|
|
|
+ textColor: '#666',
|
|
|
|
|
+ textHaloColor: '#ffffff',
|
|
|
|
|
+ textHaloWidth: 1,
|
|
|
|
|
+ textHaloBlur: 0.5
|
|
|
|
|
+ }}
|
|
|
|
|
+ />,
|
|
|
|
|
+ <MapLibreRN.CircleLayer
|
|
|
|
|
+ key={`circle_visited_must_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-visited-must-wrapper`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_visited['source-layer']}
|
|
|
|
|
+ aboveLayerID={`series-${group.id}-visited-normal`}
|
|
|
|
|
+ filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ circleRadius: 16,
|
|
|
|
|
+ circleColor: Colors.ORANGE,
|
|
|
|
|
+ circleOpacity: 1,
|
|
|
|
|
+ circleStrokeWidth: 2.5,
|
|
|
|
|
+ circleStrokeColor: Colors.ORANGE,
|
|
|
|
|
+ visibility: mustVisible ? 'visible' : 'none'
|
|
|
|
|
+ }}
|
|
|
|
|
+ />,
|
|
|
|
|
+ <MapLibreRN.SymbolLayer
|
|
|
|
|
+ key={`symbol_visited_must_${group.id}`}
|
|
|
|
|
+ id={`series-${group.id}-visited-must`}
|
|
|
|
|
+ sourceID={`series-${group.id}`}
|
|
|
|
|
+ sourceLayerID={series_visited['source-layer']}
|
|
|
|
|
+ aboveLayerID={`series-${group.id}-visited-must-wrapper`}
|
|
|
|
|
+ filter={['all', seriesVisitedFilter, ['==', 'must', 1]] as any}
|
|
|
|
|
+ style={{
|
|
|
|
|
+ symbolSpacing: 1,
|
|
|
|
|
+ iconImage: '{series_id}v',
|
|
|
|
|
+ iconAllowOverlap: true,
|
|
|
|
|
+ iconIgnorePlacement: true,
|
|
|
|
|
+ visibility: mustVisible ? 'visible' : 'none',
|
|
|
|
|
+ iconColor: '#666',
|
|
|
|
|
+ iconOpacity: 1,
|
|
|
|
|
+ iconHaloColor: '#ffffff',
|
|
|
|
|
+ iconHaloWidth: 1,
|
|
|
|
|
+ iconHaloBlur: 0.5,
|
|
|
|
|
+ textAnchor: 'top',
|
|
|
|
|
+ textField: '{name}',
|
|
|
|
|
+ textFont: ['Noto Sans Regular'],
|
|
|
|
|
+ textMaxWidth: 9,
|
|
|
|
|
+ textOffset: [0, 1.3],
|
|
|
|
|
+ textPadding: 2,
|
|
|
|
|
+ textSize: 12,
|
|
|
|
|
+ textOptional: true,
|
|
|
|
|
+ textIgnorePlacement: false,
|
|
|
|
|
+ textAllowOverlap: false,
|
|
|
|
|
+ textColor: '#666',
|
|
|
|
|
+ textHaloColor: '#ffffff',
|
|
|
|
|
+ textHaloWidth: 1,
|
|
|
|
|
+ textHaloBlur: 0.5
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('SymbolLayer render error:', error);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ })()
|
|
|
|
|
+ : null}
|
|
|
|
|
+ </React.Fragment>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ {/* <MapLibreRN.VectorSource
|
|
|
id="nomadmania_series"
|
|
id="nomadmania_series"
|
|
|
tileUrlTemplates={[VECTOR_MAP_HOST + '/tiles/series/{z}/{x}/{y}.pbf']}
|
|
tileUrlTemplates={[VECTOR_MAP_HOST + '/tiles/series/{z}/{x}/{y}.pbf']}
|
|
|
onPress={handleMarkerPress}
|
|
onPress={handleMarkerPress}
|
|
@@ -2130,7 +2374,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
|
|
|
}
|
|
}
|
|
|
})()
|
|
})()
|
|
|
: null}
|
|
: null}
|
|
|
- </MapLibreRN.VectorSource>
|
|
|
|
|
|
|
+ </MapLibreRN.VectorSource> */}
|
|
|
|
|
|
|
|
<MapLibreRN.ShapeSource
|
|
<MapLibreRN.ShapeSource
|
|
|
ref={shapeSourceRef}
|
|
ref={shapeSourceRef}
|