From db3ef7e892e109e57611ca36ec7d31c6249ed7f9 Mon Sep 17 00:00:00 2001 From: Kilian Finger Date: Tue, 22 Oct 2024 14:48:54 +0200 Subject: [PATCH 01/10] refactor: import components from react-native --- packages/examples/src/examples/Camera/Fit.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/examples/src/examples/Camera/Fit.js b/packages/examples/src/examples/Camera/Fit.js index 7cf6312c2..17ffa8695 100755 --- a/packages/examples/src/examples/Camera/Fit.js +++ b/packages/examples/src/examples/Camera/Fit.js @@ -1,8 +1,7 @@ import MapLibreGL from "@maplibre/maplibre-react-native"; import { isEqual } from "lodash"; import React from "react"; -import { View, Text } from "react-native"; -import { ScrollView, TouchableOpacity } from "react-native-gesture-handler"; +import { ScrollView, TouchableOpacity, View, Text } from "react-native"; import sheet from "../../styles/sheet"; import Page from "../common/Page"; From 882b34f0053722f3b5fe88873e29fa07fc94baba Mon Sep 17 00:00:00 2001 From: Kilian Finger Date: Tue, 22 Oct 2024 14:56:56 +0200 Subject: [PATCH 02/10] chore: setup App with SafeAreaProvider --- packages/expo-app/App.js | 69 ++++++++++++++-------------- packages/react-native-app/src/App.js | 69 ++++++++++++++-------------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/packages/expo-app/App.js b/packages/expo-app/App.js index 79a125668..10cc571a0 100644 --- a/packages/expo-app/App.js +++ b/packages/expo-app/App.js @@ -1,10 +1,12 @@ import MapLibreGL from "@maplibre/maplibre-react-native"; -import { sheet, colors } from "@maplibre-react-native/examples"; import { default as Home } from "@maplibre-react-native/examples/src/scenes/Examples"; +import colors from "@maplibre-react-native/examples/src/styles/colors"; +import sheet from "@maplibre-react-native/examples/src/styles/sheet"; import { IS_ANDROID } from "@maplibre-react-native/examples/src/utils"; -import React from "react"; -import { StyleSheet, Text, View, LogBox, SafeAreaView } from "react-native"; -import Icon from "react-native-vector-icons/MaterialIcons"; +import React, { useEffect, useState } from "react"; +import { StyleSheet, Text, View, LogBox } from "react-native"; +import { SafeAreaView, SafeAreaProvider } from "react-native-safe-area-context"; +import "react-native-gesture-handler"; LogBox.ignoreLogs([ "Warning: isMounted(...) is deprecated", @@ -19,38 +21,33 @@ const styles = StyleSheet.create({ }); MapLibreGL.setAccessToken(null); -Icon.loadFont(); -class App extends React.Component { - constructor(props) { - super(props); +export default function App() { + const [isFetchingAndroidPermission, setIsFetchingAndroidPermission] = + useState(IS_ANDROID); + const [isAndroidPermissionGranted, setIsAndroidPermissionGranted] = + useState(false); - this.state = { - isFetchingAndroidPermission: IS_ANDROID, - isAndroidPermissionGranted: false, - activeExample: -1, - }; - } + useEffect(() => { + (async () => { + if (IS_ANDROID) { + const isGranted = await MapLibreGL.requestAndroidLocationPermissions(); + + setIsAndroidPermissionGranted(isGranted); + setIsFetchingAndroidPermission(false); + } + })(); + }, []); - async componentDidMount() { - if (IS_ANDROID) { - const isGranted = await MapLibreGL.requestAndroidLocationPermissions(); - this.setState({ - isAndroidPermissionGranted: isGranted, - isFetchingAndroidPermission: false, - }); + if (IS_ANDROID && !isAndroidPermissionGranted) { + if (isFetchingAndroidPermission) { + return null; } - } - render() { - if (IS_ANDROID && !this.state.isAndroidPermissionGranted) { - if (this.state.isFetchingAndroidPermission) { - return null; - } - return ( + return ( + @@ -59,11 +56,13 @@ class App extends React.Component { - ); - } - - return ; + + ); } -} -export default App; + return ( + + + + ); +} diff --git a/packages/react-native-app/src/App.js b/packages/react-native-app/src/App.js index 79a125668..10cc571a0 100755 --- a/packages/react-native-app/src/App.js +++ b/packages/react-native-app/src/App.js @@ -1,10 +1,12 @@ import MapLibreGL from "@maplibre/maplibre-react-native"; -import { sheet, colors } from "@maplibre-react-native/examples"; import { default as Home } from "@maplibre-react-native/examples/src/scenes/Examples"; +import colors from "@maplibre-react-native/examples/src/styles/colors"; +import sheet from "@maplibre-react-native/examples/src/styles/sheet"; import { IS_ANDROID } from "@maplibre-react-native/examples/src/utils"; -import React from "react"; -import { StyleSheet, Text, View, LogBox, SafeAreaView } from "react-native"; -import Icon from "react-native-vector-icons/MaterialIcons"; +import React, { useEffect, useState } from "react"; +import { StyleSheet, Text, View, LogBox } from "react-native"; +import { SafeAreaView, SafeAreaProvider } from "react-native-safe-area-context"; +import "react-native-gesture-handler"; LogBox.ignoreLogs([ "Warning: isMounted(...) is deprecated", @@ -19,38 +21,33 @@ const styles = StyleSheet.create({ }); MapLibreGL.setAccessToken(null); -Icon.loadFont(); -class App extends React.Component { - constructor(props) { - super(props); +export default function App() { + const [isFetchingAndroidPermission, setIsFetchingAndroidPermission] = + useState(IS_ANDROID); + const [isAndroidPermissionGranted, setIsAndroidPermissionGranted] = + useState(false); - this.state = { - isFetchingAndroidPermission: IS_ANDROID, - isAndroidPermissionGranted: false, - activeExample: -1, - }; - } + useEffect(() => { + (async () => { + if (IS_ANDROID) { + const isGranted = await MapLibreGL.requestAndroidLocationPermissions(); + + setIsAndroidPermissionGranted(isGranted); + setIsFetchingAndroidPermission(false); + } + })(); + }, []); - async componentDidMount() { - if (IS_ANDROID) { - const isGranted = await MapLibreGL.requestAndroidLocationPermissions(); - this.setState({ - isAndroidPermissionGranted: isGranted, - isFetchingAndroidPermission: false, - }); + if (IS_ANDROID && !isAndroidPermissionGranted) { + if (isFetchingAndroidPermission) { + return null; } - } - render() { - if (IS_ANDROID && !this.state.isAndroidPermissionGranted) { - if (this.state.isFetchingAndroidPermission) { - return null; - } - return ( + return ( + @@ -59,11 +56,13 @@ class App extends React.Component { - ); - } - - return ; + + ); } -} -export default App; + return ( + + + + ); +} From d4c488ce8df4ded61f93b5f6e437e250c9b7945c Mon Sep 17 00:00:00 2001 From: Kilian Finger Date: Tue, 22 Oct 2024 14:58:06 +0200 Subject: [PATCH 03/10] chore: use navigation header instead of custom --- .../examples/src/examples/common/Page.tsx | 35 ++++++--------- packages/examples/src/scenes/Examples.tsx | 45 ++++++++----------- .../src/styles/{colors.js => colors.ts} | 3 ++ packages/examples/src/styles/sheet.js | 9 ---- packages/examples/src/styles/sheet.ts | 7 +++ 5 files changed, 41 insertions(+), 58 deletions(-) rename packages/examples/src/styles/{colors.js => colors.ts} (96%) delete mode 100755 packages/examples/src/styles/sheet.js create mode 100755 packages/examples/src/styles/sheet.ts diff --git a/packages/examples/src/examples/common/Page.tsx b/packages/examples/src/examples/common/Page.tsx index 8f29e5fe0..8bda4168b 100755 --- a/packages/examples/src/examples/common/Page.tsx +++ b/packages/examples/src/examples/common/Page.tsx @@ -1,32 +1,23 @@ -import { useNavigation, useRoute } from "@react-navigation/native"; -import React, { ReactElement, ReactNode } from "react"; -import { View } from "react-native"; +import React, { ReactNode } from "react"; +import { SafeAreaView } from "react-native-safe-area-context"; -import MapHeader from "./MapHeader"; -import colors from "../../styles/colors"; import sheet from "../../styles/sheet"; interface PageProps { children: ReactNode; + safeAreaView?: boolean; } -const Page = ({ children }: PageProps): ReactElement => { - const navigation = useNavigation(); - const route = useRoute(); - const label = route.name; - - return ( - - navigation.goBack()} - /> - {children} - - ); +const Page = ({ children, safeAreaView }: PageProps) => { + if (safeAreaView) { + return ( + + {children} + + ); + } else { + return children; + } }; export default Page; diff --git a/packages/examples/src/scenes/Examples.tsx b/packages/examples/src/scenes/Examples.tsx index adaa8fa4f..b9c9744c6 100644 --- a/packages/examples/src/scenes/Examples.tsx +++ b/packages/examples/src/scenes/Examples.tsx @@ -1,9 +1,5 @@ -import { NavigationContainer } from "@react-navigation/native"; -import { - createStackNavigator, - TransitionPresets, -} from "@react-navigation/stack"; -import { Icon } from "@rneui/themed"; +import { DefaultTheme, NavigationContainer } from "@react-navigation/native"; +import { createStackNavigator } from "@react-navigation/stack"; import React from "react"; import { FlatList, @@ -14,7 +10,6 @@ import { } from "react-native"; import * as MapLibreExamples from "../examples"; -import { default as MapHeader } from "../examples/common/MapHeader"; import { default as sheet } from "../styles/sheet"; const styles = StyleSheet.create({ @@ -238,15 +233,6 @@ function ExampleList({ route, navigation }: ExampleListProps) { const example = FlatExamples.find((examples) => examples.label === name) || Examples; - const back = - !(example instanceof ExampleGroup) || !example.root - ? { - onBack: () => { - navigation.goBack(); - }, - } - : {}; - function itemPress(item: any) { navigation.navigate(item.label); } @@ -257,7 +243,7 @@ function ExampleList({ route, navigation }: ExampleListProps) { itemPress(item)}> {item.label} - + @@ -266,8 +252,7 @@ function ExampleList({ route, navigation }: ExampleListProps) { return ( - - + - + + {FlatExamples.map((example) => buildNavigationScreens(example, Stack))} diff --git a/packages/examples/src/styles/colors.js b/packages/examples/src/styles/colors.ts similarity index 96% rename from packages/examples/src/styles/colors.js rename to packages/examples/src/styles/colors.ts index c60f0b6bf..43085e2ca 100755 --- a/packages/examples/src/styles/colors.js +++ b/packages/examples/src/styles/colors.ts @@ -1,4 +1,7 @@ const colors = { + blue: "#295daa", + grey: "#dedede", + primary: { blueDark: "#314ccd", blue: "#4264fb", diff --git a/packages/examples/src/styles/sheet.js b/packages/examples/src/styles/sheet.js deleted file mode 100755 index 2517609f2..000000000 --- a/packages/examples/src/styles/sheet.js +++ /dev/null @@ -1,9 +0,0 @@ -import { StyleSheet } from "react-native"; - -const styles = {}; - -styles.matchParent = { - flex: 1, -}; - -export default StyleSheet.create(styles); diff --git a/packages/examples/src/styles/sheet.ts b/packages/examples/src/styles/sheet.ts new file mode 100755 index 000000000..76c2f2e1b --- /dev/null +++ b/packages/examples/src/styles/sheet.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from "react-native"; + +export default StyleSheet.create({ + matchParent: { + flex: 1, + }, +}); From 75b34fab95d56615e932997903cf14ab9c112849 Mon Sep 17 00:00:00 2001 From: Kilian Finger Date: Tue, 22 Oct 2024 14:58:46 +0200 Subject: [PATCH 04/10] chore: replace react-native-elements usages with custom components --- packages/examples/src/assets/index.d.ts | 5 - .../examples/src/components/ButtonGroup.tsx | 95 +++++++ .../examples/Camera/SetUserTrackingModes.js | 2 +- .../FillRasterLayer/IndoorBuilding.js | 92 ------ .../FillRasterLayer/IndoorBuilding.tsx | 56 ++++ .../FillRasterLayer/WatercolorRasterTiles.js | 77 ----- .../FillRasterLayer/WatercolorRasterTiles.tsx | 47 ++++ .../src/examples/Map/ShowMapLocalStyle.tsx | 2 +- .../examples/SymbolCircleLayer/EarthQuakes.js | 202 -------------- .../SymbolCircleLayer/EarthQuakes.tsx | 262 ++++++++++++++++++ .../FollowUserLocationAlignment.tsx | 2 +- .../FollowUserLocationRenderMode.tsx | 23 +- .../UserLocation/UserLocationDisplacement.tsx | 4 +- .../src/examples/common/MapHeader.tsx | 51 ---- .../src/examples/common/TabBarPage.tsx | 64 +---- packages/examples/tsconfig.json | 3 +- 16 files changed, 490 insertions(+), 497 deletions(-) create mode 100644 packages/examples/src/components/ButtonGroup.tsx delete mode 100755 packages/examples/src/examples/FillRasterLayer/IndoorBuilding.js create mode 100755 packages/examples/src/examples/FillRasterLayer/IndoorBuilding.tsx delete mode 100755 packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.js create mode 100755 packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.tsx delete mode 100755 packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.js create mode 100755 packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.tsx delete mode 100644 packages/examples/src/examples/common/MapHeader.tsx diff --git a/packages/examples/src/assets/index.d.ts b/packages/examples/src/assets/index.d.ts index c1bbaa182..2506705cb 100644 --- a/packages/examples/src/assets/index.d.ts +++ b/packages/examples/src/assets/index.d.ts @@ -7,8 +7,3 @@ declare module "*.png" { const content: number; export default content; } - -declare module "*.json" { - const content: string; - export default content; -} diff --git a/packages/examples/src/components/ButtonGroup.tsx b/packages/examples/src/components/ButtonGroup.tsx new file mode 100644 index 000000000..184f1a994 --- /dev/null +++ b/packages/examples/src/components/ButtonGroup.tsx @@ -0,0 +1,95 @@ +import { Fragment } from "react"; +import { + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; + +import colors from "../styles/colors"; + +const styles = StyleSheet.create({ + root: { + flexDirection: "row", + borderTopWidth: 2, + borderBottomWidth: 2, + borderColor: colors.grey, + }, + + touchable: { + flex: 1, + paddingHorizontal: 16, + paddingVertical: 8, + justifyContent: "center", + alignItems: "center", + }, + touchableActive: { + backgroundColor: colors.blue, + }, + touchableText: { + textAlign: "center", + fontWeight: "bold", + }, + touchableTextActive: { + color: "#ffffff", + }, + + divider: { + width: 2, + backgroundColor: "#dedede", + }, +}); + +type ButtonGroupProps = { + value?: number; + options: string[]; + onPress: (index: number) => void; + disabled?: boolean; + scrollable?: boolean; +}; + +export function ButtonGroup({ + value, + options, + onPress, + disabled, + scrollable, +}: ButtonGroupProps) { + const buttonGroup = ( + + {options.map((option, index) => ( + + {index > 0 && } + { + onPress(index); + }} + disabled={disabled} + > + + {option} + + + + ))} + + ); + + return scrollable ? ( + + {buttonGroup} + + ) : ( + buttonGroup + ); +} diff --git a/packages/examples/src/examples/Camera/SetUserTrackingModes.js b/packages/examples/src/examples/Camera/SetUserTrackingModes.js index 6b03aa639..c8add413e 100755 --- a/packages/examples/src/examples/Camera/SetUserTrackingModes.js +++ b/packages/examples/src/examples/Camera/SetUserTrackingModes.js @@ -87,7 +87,7 @@ class SetUserTrackingModes extends React.Component { diff --git a/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.js b/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.js deleted file mode 100755 index 7f139a3b5..000000000 --- a/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.js +++ /dev/null @@ -1,92 +0,0 @@ -import MapLibreGL from "@maplibre/maplibre-react-native"; -import { Slider } from "@rneui/themed"; -import React from "react"; -import { View, StyleSheet } from "react-native"; - -import indoorMapGeoJSON from "../../assets/indoor_3d_map.json"; -import colors from "../../styles/colors"; -import sheet from "../../styles/sheet"; -import Page from "../common/Page"; - -const styles = StyleSheet.create({ - slider: { - alignItems: "stretch", - flex: 1, - justifyContent: "center", - maxHeight: 60, - paddingHorizontal: 24, - }, -}); - -const layerStyles = { - building: { - fillExtrusionOpacity: 0.5, - fillExtrusionHeight: ["get", "height"], - fillExtrusionBase: ["get", "base_height"], - fillExtrusionColor: ["get", "color"], - // fillExtrusionColorTransition: {duration: 2000, delay: 0}, - }, -}; - -class IndoorBuilding extends React.Component { - constructor(props) { - super(props); - - this.state = { - sliderValue: -80, - }; - - this.onSliderChange = this.onSliderChange.bind(this); - } - - onSliderChange(value) { - this.setState({ sliderValue: value }); - } - - render() { - return ( - - (this.map = ref)} - style={sheet.matchParent} - > - - - - - - - - - - - - - - ); - } -} - -export default IndoorBuilding; diff --git a/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.tsx b/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.tsx new file mode 100755 index 000000000..ed6fc8d3e --- /dev/null +++ b/packages/examples/src/examples/FillRasterLayer/IndoorBuilding.tsx @@ -0,0 +1,56 @@ +import MapLibreGL from "@maplibre/maplibre-react-native"; +import React, { useState } from "react"; + +import { FillExtrusionLayerStyle } from "../../../../../javascript"; +import indoorMapGeoJSON from "../../assets/indoor_3d_map.json"; +import sheet from "../../styles/sheet"; +import TabBarPage from "../common/TabBarPage"; + +const OPTIONS = [-180, -90, 0, 90, 180]; + +const layerStyles: { building: FillExtrusionLayerStyle } = { + building: { + fillExtrusionOpacity: 0.5, + fillExtrusionHeight: ["get", "height"], + fillExtrusionBase: ["get", "base_height"], + fillExtrusionColor: ["get", "color"], + fillExtrusionColorTransition: { duration: 2000, delay: 0 }, + }, +}; + +export default function IndoorBuilding() { + const [value, setValue] = useState(-90); + + return ( + ({ + label: option.toString(), + data: option, + }))} + onOptionPress={(index, data) => setValue(data)} + > + + + + + + + + + + + ); +} diff --git a/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.js b/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.js deleted file mode 100755 index 2af74dfc8..000000000 --- a/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.js +++ /dev/null @@ -1,77 +0,0 @@ -import MapLibreGL from "@maplibre/maplibre-react-native"; -import { Slider } from "@rneui/themed"; -import React from "react"; -import { View, StyleSheet } from "react-native"; - -import colors from "../../styles/colors"; -import sheet from "../../styles/sheet"; -import { SF_OFFICE_COORDINATE } from "../../utils"; -import Page from "../common/Page"; - -const styles = StyleSheet.create({ - slider: { - alignItems: "stretch", - flex: 1, - justifyContent: "center", - maxHeight: 60, - paddingHorizontal: 24, - }, -}); - -class WatercolorRasterTiles extends React.Component { - constructor(props) { - super(props); - - this.state = { - opacity: 1, - }; - - this.onOpacityChange = this.onOpacityChange.bind(this); - } - - onOpacityChange(value) { - this.setState({ opacity: value }); - } - - render() { - const rasterSourceProps = { - id: "stamenWatercolorSource", - tileUrlTemplates: [ - "https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg", - ], - tileSize: 256, - }; - - return ( - - - - - - - - - - - - - - ); - } -} - -export default WatercolorRasterTiles; diff --git a/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.tsx b/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.tsx new file mode 100755 index 000000000..4913e087d --- /dev/null +++ b/packages/examples/src/examples/FillRasterLayer/WatercolorRasterTiles.tsx @@ -0,0 +1,47 @@ +import MapLibreGL from "@maplibre/maplibre-react-native"; +import React, { useState } from "react"; + +import sheet from "../../styles/sheet"; +import { SF_OFFICE_COORDINATE } from "../../utils"; +import TabBarPage from "../common/TabBarPage"; + +const OPTIONS = [0, 0.25, 0.5, 0.75, 1]; +const DEFAULT_OPTION = 4; + +export default function WatercolorRasterTiles() { + const [value, setValue] = useState(OPTIONS[DEFAULT_OPTION]); + + const rasterSourceProps = { + id: "stamenWatercolorSource", + tileUrlTemplates: [ + "https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg", + ], + tileSize: 256, + }; + + return ( + ({ + label: option.toString(), + data: option, + }))} + onOptionPress={(index, data) => setValue(data)} + > + + + + + + + + + ); +} diff --git a/packages/examples/src/examples/Map/ShowMapLocalStyle.tsx b/packages/examples/src/examples/Map/ShowMapLocalStyle.tsx index dc346f0cd..23c136000 100644 --- a/packages/examples/src/examples/Map/ShowMapLocalStyle.tsx +++ b/packages/examples/src/examples/Map/ShowMapLocalStyle.tsx @@ -22,7 +22,7 @@ const ShowMap: FC = (props) => { }; return ( - + diff --git a/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.js b/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.js deleted file mode 100755 index 0f2b427da..000000000 --- a/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.js +++ /dev/null @@ -1,202 +0,0 @@ -import MapLibreGL from "@maplibre/maplibre-react-native"; -import { Overlay, ListItem, FAB, Icon } from "@rneui/themed"; -import moment from "moment"; -import React from "react"; -import { FlatList } from "react-native"; - -import earthQuakesJSON from "../../assets/earthquakes.json"; -import sheet from "../../styles/sheet"; -import { SF_OFFICE_COORDINATE } from "../../utils"; -import Page from "../common/Page"; - -const layerStyles = { - singlePoint: { - circleColor: "green", - circleOpacity: 0.84, - circleStrokeWidth: 2, - circleStrokeColor: "white", - circleRadius: 5, - circlePitchAlignment: "map", - }, - - clusteredPoints: { - circlePitchAlignment: "map", - - circleColor: [ - "step", - ["get", "point_count"], - "#51bbd6", - 100, - "#f1f075", - 750, - "#f28cb1", - ], - - circleRadius: ["step", ["get", "point_count"], 20, 100, 30, 750, 40], - - circleOpacity: 0.84, - circleStrokeWidth: 2, - circleStrokeColor: "white", - }, - - clusterCount: { - textField: [ - "format", - ["concat", ["get", "point_count"], "\n"], - {}, - [ - "concat", - ">1: ", - [ - "+", - ["get", "mag2"], - ["get", "mag3"], - ["get", "mag4"], - ["get", "mag5"], - ], - ], - { "font-scale": 0.8 }, - ], - textSize: 12, - textPitchAlignment: "map", - }, -}; - -const styles = { - fab: { - position: "absolute", - top: 10, - right: 10, - elevation: 9999, - zIndex: 9999, - }, -}; - -const mag1 = ["<", ["get", "mag"], 2]; -const mag2 = ["all", [">=", ["get", "mag"], 2], ["<", ["get", "mag"], 3]]; -const mag3 = ["all", [">=", ["get", "mag"], 3], ["<", ["get", "mag"], 4]]; -const mag4 = ["all", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]]; -const mag5 = [">=", ["get", "mag"], 5]; -class EarthQuakes extends React.Component { - state = { - selectedCluster: null, - }; - - render() { - return ( - <> - - { - this.setState({ selectedCluster: null }); - }} - icon={} - size="large" - style={styles.fab} - /> - {this.state.selectedCluster && ( - { - return earthquakeInfo.code; - }} - data={this.state.selectedCluster.features} - renderItem={({ item: { properties: earthquakeInfo } }) => { - const magnitude = `Magnitude: ${earthquakeInfo.mag}`; - const place = `Place: ${earthquakeInfo.place}`; - const code = `Code: ${earthquakeInfo.code}`; - const time = `Time: ${moment(earthquakeInfo.time).format( - "MMMM Do YYYY, h:mm:ss a", - )}`; - - return ( - - - {earthquakeInfo.title} - {magnitude} - {place} - {code} - {time} - - - ); - }} - /> - )} - - - - - - { - const cluster = shape.features[0]; - const collection = await this.shape.getClusterLeaves( - cluster, - 999, - 0, - ); - - this.setState({ selectedCluster: collection }); - }} - ref={(shape) => (this.shape = shape)} - cluster - clusterRadius={50} - clusterMaxZoom={14} - clusterProperties={{ - mag1: [ - ["+", ["accumulated"], ["get", "mag1"]], - ["case", mag1, 1, 0], - ], - mag2: [ - ["+", ["accumulated"], ["get", "mag2"]], - ["case", mag2, 1, 0], - ], - mag3: [ - ["+", ["accumulated"], ["get", "mag3"]], - ["case", mag3, 1, 0], - ], - mag4: [ - ["+", ["accumulated"], ["get", "mag4"]], - ["case", mag4, 1, 0], - ], - mag5: [ - ["+", ["accumulated"], ["get", "mag5"]], - ["case", mag5, 1, 0], - ], - }} - shape={earthQuakesJSON} - > - - - - - - - - - - ); - } -} - -export default EarthQuakes; diff --git a/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.tsx b/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.tsx new file mode 100755 index 000000000..41c028638 --- /dev/null +++ b/packages/examples/src/examples/SymbolCircleLayer/EarthQuakes.tsx @@ -0,0 +1,262 @@ +import MapLibreGL from "@maplibre/maplibre-react-native"; +import { FeatureCollection } from "geojson"; +import moment from "moment"; +import React, { useRef, useState } from "react"; +import { + FlatList, + Modal, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; + +import { + CircleLayerStyle, + ShapeSourceRef, + SymbolLayerStyle, +} from "../../../../../javascript"; +import earthQuakesJSON from "../../assets/earthquakes.json"; +import colors from "../../styles/colors"; +import sheet from "../../styles/sheet"; +import { SF_OFFICE_COORDINATE } from "../../utils"; +import Page from "../common/Page"; + +const layerStyles: { + singlePoint: CircleLayerStyle; + clusteredPoints: CircleLayerStyle; + clusterCount: SymbolLayerStyle; +} = { + singlePoint: { + circleColor: "green", + circleOpacity: 0.84, + circleStrokeWidth: 2, + circleStrokeColor: "white", + circleRadius: 5, + circlePitchAlignment: "map", + }, + + clusteredPoints: { + circlePitchAlignment: "map", + circleColor: [ + "step", + ["get", "point_count"], + "#51bbd6", + 100, + "#f1f075", + 750, + "#f28cb1", + ], + circleRadius: ["step", ["get", "point_count"], 20, 100, 30, 750, 40], + circleOpacity: 0.84, + circleStrokeWidth: 2, + circleStrokeColor: "white", + }, + + clusterCount: { + textField: [ + "format", + ["concat", ["get", "point_count"], "\n"], + {}, + [ + "concat", + ">1: ", + [ + "+", + ["get", "mag2"], + ["get", "mag3"], + ["get", "mag4"], + ["get", "mag5"], + ], + ], + { "font-scale": 0.8 }, + ], + textSize: 12, + textPitchAlignment: "map", + }, +}; + +const styles = StyleSheet.create({ + header: { + paddingHorizontal: 16, + paddingVertical: 8, + flexDirection: "row", + justifyContent: "space-between", + backgroundColor: "#ffffff", + borderBottomWidth: 2, + borderBottomColor: colors.grey, + }, + headerText: { + fontSize: 20, + fontWeight: "bold", + }, + touchable: { + backgroundColor: colors.blue, + width: 32, + height: 32, + borderRadius: 16, + justifyContent: "center", + alignItems: "center", + }, + touchableText: { + color: "#ffffff", + fontSize: 20, + fontWeight: "bold", + lineHeight: 20, + }, + listItem: { + paddingHorizontal: 16, + paddingVertical: 8, + }, + listItemTitle: { + fontSize: 20, + fontWeight: "bold", + }, +}); + +const mag1 = ["<", ["get", "mag"], 2]; +const mag2 = ["all", [">=", ["get", "mag"], 2], ["<", ["get", "mag"], 3]]; +const mag3 = ["all", [">=", ["get", "mag"], 3], ["<", ["get", "mag"], 4]]; +const mag4 = ["all", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]]; +const mag5 = [">=", ["get", "mag"], 5]; + +export default function EarthQuakes() { + const shapeSource = useRef(null); + const [cluster, setCluster] = useState(); + + return ( + + + + + {cluster && ( + { + return ( + + + Earthquakes ({cluster.features.length}) + + { + setCluster(undefined); + }} + style={styles.touchable} + > + + + + ); + }} + keyExtractor={({ properties: earthquakeInfo }) => { + return earthquakeInfo?.code; + }} + data={cluster.features} + renderItem={({ item: { properties: earthquakeInfo } }) => { + const magnitude = `Magnitude: ${earthquakeInfo?.mag}`; + const place = `Place: ${earthquakeInfo?.place}`; + const code = `Code: ${earthquakeInfo?.code}`; + const time = `Time: ${moment(earthquakeInfo?.time).format( + "MMMM Do YYYY, h:mm:ss a", + )}`; + + return ( + + + {earthquakeInfo?.title} + + {magnitude} + {place} + {code} + {time} + + ); + }} + /> + )} + + + + + + + + + { + const cluster = event.features[0]; + + console.log(cluster.type); + if (cluster.type === "Feature") { + const collection = await shapeSource.current?.getClusterLeaves( + // TODO: improve once GeoJSON types are aligned + // @ts-ignore + cluster, + 999, + 0, + ); + + setCluster(collection as FeatureCollection); + } + }} + cluster + clusterRadius={50} + clusterMaxZoomLevel={14} + clusterProperties={{ + mag1: [ + ["+", ["accumulated"], ["get", "mag1"]], + ["case", mag1, 1, 0], + ], + mag2: [ + ["+", ["accumulated"], ["get", "mag2"]], + ["case", mag2, 1, 0], + ], + mag3: [ + ["+", ["accumulated"], ["get", "mag3"]], + ["case", mag3, 1, 0], + ], + mag4: [ + ["+", ["accumulated"], ["get", "mag4"]], + ["case", mag4, 1, 0], + ], + mag5: [ + ["+", ["accumulated"], ["get", "mag5"]], + ["case", mag5, 1, 0], + ], + }} + > + + + + + + + + + + ); +} diff --git a/packages/examples/src/examples/UserLocation/FollowUserLocationAlignment.tsx b/packages/examples/src/examples/UserLocation/FollowUserLocationAlignment.tsx index c2cd2786f..98957bd4c 100755 --- a/packages/examples/src/examples/UserLocation/FollowUserLocationAlignment.tsx +++ b/packages/examples/src/examples/UserLocation/FollowUserLocationAlignment.tsx @@ -20,7 +20,7 @@ export default function FollowUserLocationAlignment() { return ( ({ label: alignmentValue, data: alignmentValue, diff --git a/packages/examples/src/examples/UserLocation/FollowUserLocationRenderMode.tsx b/packages/examples/src/examples/UserLocation/FollowUserLocationRenderMode.tsx index 66a3b0724..11e70345f 100755 --- a/packages/examples/src/examples/UserLocation/FollowUserLocationRenderMode.tsx +++ b/packages/examples/src/examples/UserLocation/FollowUserLocationRenderMode.tsx @@ -2,10 +2,10 @@ import MapLibreGL, { UserLocationRenderMode, UserTrackingMode, } from "@maplibre/maplibre-react-native"; -import { ButtonGroup } from "@rneui/themed"; import React, { ReactNode, useState } from "react"; import { Button, Platform, Text, View } from "react-native"; +import { ButtonGroup } from "../../components/ButtonGroup"; import Page from "../common/Page"; const SettingsGroup = ({ @@ -55,7 +55,7 @@ export default function FollowUserLocationRenderMode() { >("normal"); return ( - +