diff --git a/docs/GEOJSON.md b/docs/GEOJSON.md
new file mode 100644
index 0000000..cf0d20b
--- /dev/null
+++ b/docs/GEOJSON.md
@@ -0,0 +1,98 @@
+# GeoJson
+
+Renders [GeoJSON](https://geojson.org/) data on the map using Marker, Polyline, and Polygon components.
+
+## Usage
+
+```tsx
+import { MapView, GeoJson } from '@lugg/maps';
+
+const geojson = {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Point',
+ coordinates: [-122.4194, 37.7749],
+ },
+ properties: { title: 'San Francisco' },
+ },
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [[
+ [-122.428, 37.784],
+ [-122.422, 37.784],
+ [-122.422, 37.779],
+ [-122.428, 37.779],
+ [-122.428, 37.784],
+ ]],
+ },
+ properties: { fill: 'rgba(66, 133, 244, 0.3)', stroke: '#4285F4' },
+ },
+ ],
+};
+
+
+
+
+```
+
+### Custom Rendering
+
+Use render callbacks to customize how features are rendered:
+
+```tsx
+ (
+
+
+
+ )}
+ renderPolygon={(props, feature) => (
+
+ )}
+/>
+```
+
+## Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `geojson` | `GeoJSON` | **required** | GeoJSON object (FeatureCollection, Feature, or Geometry) |
+| `strokeColor` | `ColorValue` | - | Default stroke color for polylines and polygons |
+| `strokeWidth` | `number` | - | Default stroke width |
+| `fillColor` | `ColorValue` | - | Default fill color for polygons |
+| `zIndex` | `number` | - | Z-index for all rendered components |
+| `renderMarker` | `(props, feature) => ReactElement` | - | Custom marker renderer |
+| `renderPolyline` | `(props, feature) => ReactElement` | - | Custom polyline renderer |
+| `renderPolygon` | `(props, feature) => ReactElement` | - | Custom polygon renderer |
+
+## Geometry Mapping
+
+| GeoJSON Type | Renders As |
+|---|---|
+| Point | `` |
+| MultiPoint | Multiple `` |
+| LineString | `` |
+| MultiLineString | Multiple `` |
+| Polygon | `` (with holes) |
+| MultiPolygon | Multiple `` |
+| GeometryCollection | Recursive rendering |
+
+## Feature Properties (simplestyle-spec)
+
+Per-feature styling via `feature.properties` overrides component-level props:
+
+| Property | Maps To |
+|---|---|
+| `title` | Marker `title` |
+| `description` | Marker `description` |
+| `stroke` | Polyline `strokeColors[0]` / Polygon `strokeColor` |
+| `stroke-width` | Polyline/Polygon `strokeWidth` |
+| `fill` | Polygon `fillColor` |
+
+Precedence: render callback > feature properties > component props.
diff --git a/example/shared/src/Home.tsx b/example/shared/src/Home.tsx
index 67ba56d..3a1572d 100644
--- a/example/shared/src/Home.tsx
+++ b/example/shared/src/Home.tsx
@@ -2,8 +2,9 @@ import { useRef, useState, useCallback } from 'react';
import {
StyleSheet,
View,
- Text,
+ TextInput,
Platform,
+ useColorScheme,
useWindowDimensions,
} from 'react-native';
import {
@@ -12,6 +13,7 @@ import {
type MapProviderType,
type MapCameraEvent,
type MapPressEvent,
+ type GeoJSON,
} from '@lugg/maps';
import {
TrueSheet,
@@ -24,7 +26,7 @@ import {
useReanimatedTrueSheet,
} from '@lodev09/react-native-true-sheet/reanimated';
-import { Button, Map } from './components';
+import { Button, Map, ThemedText } from './components';
import { randomFrom, randomLetter } from './utils';
import {
MARKER_COLORS,
@@ -34,6 +36,17 @@ import {
} from './markers';
import { useLocationPermission } from './useLocationPermission';
+const GEOJSON_PRESETS = [
+ {
+ name: 'California Counties',
+ url: 'https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/california-counties.geojson',
+ },
+ {
+ name: 'San Francisco Neighborhoods',
+ url: 'https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/san-francisco.geojson',
+ },
+];
+
const bottomEdgeInsets = (bottom: number) => ({
top: 0,
left: 0,
@@ -41,7 +54,7 @@ const bottomEdgeInsets = (bottom: number) => ({
right: 0,
});
-export function Home() {
+export const Home = () => {
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
return (
@@ -53,18 +66,23 @@ export function Home() {
);
-}
+};
-function HomeContent() {
+const HomeContent = () => {
const mapRef = useRef(null);
const sheetRef = useRef(null);
+ const geojsonSheetRef = useRef(null);
const { height: screenHeight } = useWindowDimensions();
+ const isDark = useColorScheme() === 'dark';
const locationPermission = useLocationPermission();
const { animatedPosition } = useReanimatedTrueSheet();
const [provider, setProvider] = useState('apple');
const [showMap, setShowMap] = useState(true);
const [markers, setMarkers] = useState(INITIAL_MARKERS);
- const [statusText, setStatusText] = useState('Loading...');
+ const [status, setStatus] = useState({ text: 'Loading...', error: false });
+ const [geojson, setGeojson] = useState(null);
+ const [geojsonUrl, setGeojsonUrl] = useState('');
+ const [loadingGeojson, setLoadingGeojson] = useState(false);
const lastCoordinate = useRef({ latitude: 37.78, longitude: -122.43 });
const statusLockRef = useRef(false);
@@ -111,7 +129,10 @@ function HomeContent() {
const lng = coordinate.longitude.toFixed(5);
const px = point.x.toFixed(0);
const py = point.y.toFixed(0);
- setStatusText(`${label}: ${lat}, ${lng} (${px}, ${py})`);
+ setStatus({
+ text: `${label}: ${lat}, ${lng} (${px}, ${py})`,
+ error: false,
+ });
},
[lockStatus]
);
@@ -129,7 +150,7 @@ function HomeContent() {
: gesture
? ' (gesture)'
: '';
- setStatusText(pos + suffix);
+ setStatus({ text: pos + suffix, error: false });
},
[]
);
@@ -178,6 +199,24 @@ function HomeContent() {
});
};
+ const loadGeojson = async (url: string) => {
+ if (!url.trim()) return;
+ setLoadingGeojson(true);
+ lockStatus();
+ setStatus({ text: 'Loading GeoJSON...', error: false });
+ try {
+ const res = await fetch(url.trim());
+ const data = await res.json();
+ setGeojson(data);
+ setStatus({ text: 'GeoJSON loaded', error: false });
+ geojsonSheetRef.current?.dismiss();
+ } catch (e: any) {
+ setStatus({ text: `GeoJSON: ${e.message}`, error: true });
+ } finally {
+ setLoadingGeojson(false);
+ }
+ };
+
return (
{showMap && (
@@ -186,6 +225,7 @@ function HomeContent() {
ref={mapRef}
provider={provider}
markers={markers}
+ geojson={geojson}
animatedPosition={animatedPosition}
userLocationEnabled={locationPermission}
onReady={handleMapReady}
@@ -206,7 +246,7 @@ function HomeContent() {
onMarkerDragEnd={(e, m) => formatPressEvent(e, `Drag end(${m.name})`)}
onPolygonPress={() => {
lockStatus();
- setStatusText('Polygon pressed');
+ setStatus({ text: 'Polygon pressed', error: false });
}}
/>
)}
@@ -223,48 +263,117 @@ function HomeContent() {
onDidPresent={handleSheetPresent}
onDetentChange={handleDetentChange}
>
- {statusText}
+
+ {status.text}
+
-
+
+
+ Load GeoJSON
+
+ loadGeojson(geojsonUrl)}
+ disabled={loadingGeojson || !geojsonUrl.trim()}
+ />
+ Presets
+ {GEOJSON_PRESETS.map((preset) => (
+ {
+ setGeojsonUrl(preset.url);
+ loadGeojson(preset.url);
+ }}
+ disabled={loadingGeojson}
+ />
+ ))}
+ {geojson && (
+ {
+ setGeojson(null);
+ setGeojsonUrl('');
+ geojsonSheetRef.current?.dismiss();
+ }}
+ />
+ )}
+
);
-}
+};
const styles = StyleSheet.create({
container: { flex: 1 },
statusText: {
- fontSize: 14,
color: '#666',
},
+ statusError: {
+ color: '#D32F2F',
+ },
sheet: {
padding: 24,
gap: 12,
@@ -272,6 +381,28 @@ const styles = StyleSheet.create({
sheetContent: {
flexDirection: 'row',
flexWrap: 'wrap',
+ gap: 8,
+ },
+ sheetButton: {
+ flex: 1,
+ minWidth: '45%',
+ },
+ geojsonSheet: {
+ padding: 24,
gap: 12,
},
+ urlInput: {
+ borderWidth: StyleSheet.hairlineWidth,
+ borderColor: '#DDD',
+ borderRadius: 8,
+ padding: 12,
+ fontSize: 14,
+ backgroundColor: '#FFF',
+ color: '#000',
+ },
+ urlInputDark: {
+ backgroundColor: '#1C1C1E',
+ borderColor: '#333',
+ color: '#FFF',
+ },
});
diff --git a/example/shared/src/components/Button.tsx b/example/shared/src/components/Button.tsx
index 406c87c..7d1e8a4 100644
--- a/example/shared/src/components/Button.tsx
+++ b/example/shared/src/components/Button.tsx
@@ -1,50 +1,51 @@
-import { Pressable, Text, StyleSheet, type PressableProps } from 'react-native';
+import {
+ Pressable,
+ Text,
+ StyleSheet,
+ type PressableProps,
+ type ViewStyle,
+} from 'react-native';
interface ButtonProps extends Omit {
title: string;
+ style?: ViewStyle;
}
-export function Button({ title, disabled, ...props }: ButtonProps) {
+export const Button = ({ title, disabled, style, ...props }: ButtonProps) => {
return (
[
styles.button,
pressed && !disabled && styles.pressed,
disabled && styles.disabled,
+ style,
]}
disabled={disabled}
{...props}
>
-
- {title}
-
+ {title}
);
-}
+};
const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
+ height: 40,
paddingHorizontal: 16,
- paddingVertical: 10,
- borderRadius: 8,
+ borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
- flexGrow: 1,
- minWidth: '45%',
},
pressed: {
opacity: 0.7,
},
disabled: {
- backgroundColor: '#A0A0A0',
+ opacity: 0.4,
},
text: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
- textDisabled: {
- color: '#E0E0E0',
- },
});
diff --git a/example/shared/src/components/CrewMarker.tsx b/example/shared/src/components/CrewMarker.tsx
index 4c5579c..184661b 100644
--- a/example/shared/src/components/CrewMarker.tsx
+++ b/example/shared/src/components/CrewMarker.tsx
@@ -52,12 +52,12 @@ const getScaleForZoom = (zoom: number) => {
return MIN_SCALE + t * (MAX_SCALE - MIN_SCALE);
};
-export function CrewMarker({
+export const CrewMarker = ({
route,
loaded = false,
speed = 1,
zoom = BASE_ZOOM,
-}: CrewMarkerProps) {
+}: CrewMarkerProps) => {
const latitude = useSharedValue(route[0]?.latitude ?? 0);
const longitude = useSharedValue(route[0]?.longitude ?? 0);
const bearingValue = useSharedValue(0);
@@ -143,4 +143,4 @@ export function CrewMarker({
);
-}
+};
diff --git a/example/shared/src/components/CrewMarker.web.tsx b/example/shared/src/components/CrewMarker.web.tsx
index 3c4105e..6513e36 100644
--- a/example/shared/src/components/CrewMarker.web.tsx
+++ b/example/shared/src/components/CrewMarker.web.tsx
@@ -40,12 +40,12 @@ const getScaleForZoom = (zoom: number) => {
return MIN_SCALE + t * (MAX_SCALE - MIN_SCALE);
};
-export function CrewMarker({
+export const CrewMarker = ({
route,
loaded = false,
speed = 1,
zoom = BASE_ZOOM,
-}: CrewMarkerProps) {
+}: CrewMarkerProps) => {
const [coordinate, setCoordinate] = useState(
route[0] ?? { latitude: 0, longitude: 0 }
);
@@ -128,4 +128,4 @@ export function CrewMarker({
);
-}
+};
diff --git a/example/shared/src/components/Map.tsx b/example/shared/src/components/Map.tsx
index 0dd8f2f..79fa229 100644
--- a/example/shared/src/components/Map.tsx
+++ b/example/shared/src/components/Map.tsx
@@ -3,11 +3,13 @@ import { StyleSheet, View, useWindowDimensions } from 'react-native';
import {
MapView,
Marker,
+ GeoJson,
Polygon,
type MapViewProps,
type MapCameraEvent,
type MarkerPressEvent,
type MarkerDragEvent,
+ type GeoJSON,
} from '@lugg/maps';
import Animated, {
useAnimatedStyle,
@@ -20,9 +22,11 @@ import { MarkerText } from './MarkerText';
import { MarkerImage } from './MarkerImage';
import type { MarkerData } from './index';
import { Route, smoothCoordinates } from './Route';
+import { SAMPLE_GEOJSON } from '../geojson';
interface MapProps extends MapViewProps {
markers: MarkerData[];
+ geojson?: GeoJSON | null;
animatedPosition?: SharedValue;
onPolygonPress?: () => void;
onMarkerPress?: (event: MarkerPressEvent, marker: MarkerData) => void;
@@ -176,6 +180,7 @@ export const Map = forwardRef(
(
{
markers,
+ geojson,
edgeInsets,
animatedPosition,
onCameraIdle,
@@ -260,6 +265,21 @@ export const Map = forwardRef(
text="LO"
color="#34A853"
/>
+
+ {geojson && (
+ (
+
+ )}
+ />
+ )}
diff --git a/example/shared/src/components/MarkerIcon.tsx b/example/shared/src/components/MarkerIcon.tsx
index 09035d5..8398b17 100644
--- a/example/shared/src/components/MarkerIcon.tsx
+++ b/example/shared/src/components/MarkerIcon.tsx
@@ -3,10 +3,10 @@ import { Marker, type MarkerProps } from '@lugg/maps';
interface MarkerIconProps extends MarkerProps {}
-export function MarkerIcon({
+export const MarkerIcon = ({
anchor = { x: 0.5, y: 1 },
...rest
-}: MarkerIconProps) {
+}: MarkerIconProps) => {
return (
);
-}
+};
diff --git a/example/shared/src/components/MarkerImage.tsx b/example/shared/src/components/MarkerImage.tsx
index d9febb9..5c2a1f8 100644
--- a/example/shared/src/components/MarkerImage.tsx
+++ b/example/shared/src/components/MarkerImage.tsx
@@ -6,12 +6,12 @@ interface MarkerImageProps extends MarkerProps {
size?: number;
}
-export function MarkerImage({
+export const MarkerImage = ({
source,
size = 40,
anchor = { x: 0.5, y: 0.5 },
...rest
-}: MarkerImageProps) {
+}: MarkerImageProps) => {
return (
);
-}
+};
const styles = StyleSheet.create({
image: {
diff --git a/example/shared/src/components/MarkerText.tsx b/example/shared/src/components/MarkerText.tsx
index ec8635c..a03c8e9 100644
--- a/example/shared/src/components/MarkerText.tsx
+++ b/example/shared/src/components/MarkerText.tsx
@@ -6,12 +6,12 @@ interface MarkerTextProps extends MarkerProps {
color?: string;
}
-export function MarkerText({
+export const MarkerText = ({
text,
color = '#EA4335',
anchor = { x: 0.5, y: 0.5 },
...rest
-}: MarkerTextProps) {
+}: MarkerTextProps) => {
return (
@@ -19,7 +19,7 @@ export function MarkerText({
);
-}
+};
const styles = StyleSheet.create({
container: {
diff --git a/example/shared/src/components/ThemedText.tsx b/example/shared/src/components/ThemedText.tsx
new file mode 100644
index 0000000..0549e32
--- /dev/null
+++ b/example/shared/src/components/ThemedText.tsx
@@ -0,0 +1,35 @@
+import { Text, type TextProps, StyleSheet, useColorScheme } from 'react-native';
+
+interface ThemedTextProps extends TextProps {
+ variant?: 'body' | 'title' | 'caption';
+}
+
+export const ThemedText = ({
+ variant = 'body',
+ style,
+ ...props
+}: ThemedTextProps) => {
+ const isDark = useColorScheme() === 'dark';
+
+ return (
+
+ );
+};
+
+const styles = StyleSheet.create({
+ body: {
+ fontSize: 14,
+ },
+ title: {
+ fontSize: 18,
+ fontWeight: '700',
+ },
+ caption: {
+ fontSize: 14,
+ fontWeight: '600',
+ opacity: 0.5,
+ },
+});
diff --git a/example/shared/src/components/index.ts b/example/shared/src/components/index.ts
index 5794a2a..8334b6c 100644
--- a/example/shared/src/components/index.ts
+++ b/example/shared/src/components/index.ts
@@ -1,4 +1,5 @@
export { Button } from './Button';
+export { ThemedText } from './ThemedText';
export { Map } from './Map';
export { MarkerIcon } from './MarkerIcon';
export { MarkerText } from './MarkerText';
diff --git a/example/shared/src/geojson.ts b/example/shared/src/geojson.ts
new file mode 100644
index 0000000..be78498
--- /dev/null
+++ b/example/shared/src/geojson.ts
@@ -0,0 +1,85 @@
+import type { GeoJSON } from '@lugg/maps';
+
+export const SAMPLE_GEOJSON: GeoJSON = {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [
+ [
+ [-122.42, 37.775],
+ [-122.41, 37.775],
+ [-122.41, 37.765],
+ [-122.42, 37.765],
+ [-122.42, 37.775],
+ ],
+ ],
+ },
+ properties: {
+ title: 'GeoJSON Polygon',
+ fill: 'rgba(255, 0, 0, 0.3)',
+ stroke: '#FF0000',
+ 'stroke-width': 2,
+ },
+ },
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'Point',
+ coordinates: [-122.415, 37.77],
+ },
+ properties: { title: 'GeoJSON Point' },
+ },
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'MultiPoint',
+ coordinates: [
+ [-122.408, 37.772],
+ [-122.405, 37.768],
+ ],
+ },
+ properties: { title: 'GeoJSON MultiPoint' },
+ },
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'LineString',
+ coordinates: [
+ [-122.42, 37.762],
+ [-122.415, 37.758],
+ [-122.41, 37.76],
+ [-122.405, 37.757],
+ ],
+ },
+ properties: {
+ title: 'GeoJSON LineString',
+ stroke: '#FF0000',
+ 'stroke-width': 3,
+ },
+ },
+ {
+ type: 'Feature',
+ geometry: {
+ type: 'MultiLineString',
+ coordinates: [
+ [
+ [-122.422, 37.758],
+ [-122.418, 37.755],
+ ],
+ [
+ [-122.416, 37.755],
+ [-122.412, 37.752],
+ ],
+ ],
+ },
+ properties: {
+ title: 'GeoJSON MultiLineString',
+ stroke: '#FF9800',
+ 'stroke-width': 2,
+ },
+ },
+ ],
+};
diff --git a/example/shared/src/useLocationPermission.ts b/example/shared/src/useLocationPermission.ts
index d88f656..a3469b0 100644
--- a/example/shared/src/useLocationPermission.ts
+++ b/example/shared/src/useLocationPermission.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { Platform, PermissionsAndroid } from 'react-native';
-export function useLocationPermission() {
+export const useLocationPermission = () => {
const [granted, setGranted] = useState(false);
useEffect(() => {
@@ -32,4 +32,4 @@ export function useLocationPermission() {
}, []);
return granted;
-}
+};
diff --git a/src/MapProvider.tsx b/src/MapProvider.tsx
index d38143d..9d63326 100644
--- a/src/MapProvider.tsx
+++ b/src/MapProvider.tsx
@@ -5,6 +5,4 @@ import type { MapProviderProps } from './MapProvider.types';
* On web, wraps children with Google Maps APIProvider.
* On native, passes children through.
*/
-export function MapProvider({ children }: MapProviderProps) {
- return children;
-}
+export const MapProvider = ({ children }: MapProviderProps) => children;
diff --git a/src/MapProvider.web.tsx b/src/MapProvider.web.tsx
index a316ef3..57fb838 100644
--- a/src/MapProvider.web.tsx
+++ b/src/MapProvider.web.tsx
@@ -10,6 +10,6 @@ export const MapContext = createContext<{
export const useMapContext = () => useContext(MapContext);
-export function MapProvider({ apiKey = '', children }: MapProviderProps) {
- return {children};
-}
+export const MapProvider = ({ apiKey = '', children }: MapProviderProps) => (
+ {children}
+);
diff --git a/src/MapView.web.tsx b/src/MapView.web.tsx
index b3f1152..16bee12 100644
--- a/src/MapView.web.tsx
+++ b/src/MapView.web.tsx
@@ -59,7 +59,7 @@ const userLocationDotStyle: CSSProperties = {
boxShadow: '0 1px 4px rgba(0,0,0,0.3)',
};
-function UserLocationMarker({ enabled }: { enabled?: boolean }) {
+const UserLocationMarker = ({ enabled }: { enabled?: boolean }) => {
const [coordinate, setCoordinate] = useState(null);
useEffect(() => {
@@ -94,7 +94,7 @@ function UserLocationMarker({ enabled }: { enabled?: boolean }) {
);
-}
+};
export const MapView = forwardRef(function MapView(
props,
diff --git a/src/components/GeoJson.tsx b/src/components/GeoJson.tsx
new file mode 100644
index 0000000..b5e4caa
--- /dev/null
+++ b/src/components/GeoJson.tsx
@@ -0,0 +1,221 @@
+import React, { useMemo, type ReactElement } from 'react';
+import type {
+ Feature,
+ FeatureCollection,
+ GeoJSON,
+ Geometry,
+ Position,
+} from './GeoJson.types';
+import type { Coordinate } from '../types';
+import type { GeoJsonProps } from './GeoJson.types';
+import { Marker } from './Marker';
+import type { MarkerProps } from './Marker.types';
+import { Polygon } from './Polygon';
+import type { PolygonProps } from './Polygon.types';
+import { Polyline } from './Polyline';
+import type { PolylineProps } from './Polyline.types';
+
+export type { GeoJsonProps } from './GeoJson.types';
+
+const toCoordinate = (position: Position): Coordinate => ({
+ latitude: position[1],
+ longitude: position[0],
+});
+
+const toCoordinates = (positions: Position[]): Coordinate[] =>
+ positions.map(toCoordinate);
+
+const normalizeFeatures = (geojson: GeoJSON): Feature[] => {
+ switch (geojson.type) {
+ case 'FeatureCollection':
+ return (geojson as FeatureCollection).features;
+ case 'Feature':
+ return [geojson as Feature];
+ default:
+ return [
+ { type: 'Feature', geometry: geojson as Geometry, properties: null },
+ ];
+ }
+};
+
+const renderGeometry = (
+ geometry: Geometry,
+ feature: Feature,
+ props: GeoJsonProps,
+ keyPrefix: string
+): ReactElement[] => {
+ const elements: ReactElement[] = [];
+
+ switch (geometry.type) {
+ case 'Point': {
+ const markerProps: MarkerProps = {
+ coordinate: toCoordinate(geometry.coordinates),
+ title: feature.properties?.title,
+ description: feature.properties?.description,
+ zIndex: props.zIndex,
+ };
+ elements.push(
+ props.renderMarker ? (
+ props.renderMarker(markerProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ break;
+ }
+ case 'MultiPoint': {
+ for (let i = 0; i < geometry.coordinates.length; i++) {
+ const markerProps: MarkerProps = {
+ coordinate: toCoordinate(geometry.coordinates[i]!),
+ title: feature.properties?.title,
+ description: feature.properties?.description,
+ zIndex: props.zIndex,
+ };
+ const key = `${keyPrefix}-${i}`;
+ elements.push(
+ props.renderMarker ? (
+ props.renderMarker(markerProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ }
+ break;
+ }
+ case 'LineString': {
+ const p = feature.properties;
+ const polylineProps: PolylineProps = {
+ coordinates: toCoordinates(geometry.coordinates),
+ strokeColors: p?.stroke ? [p.stroke] : undefined,
+ strokeWidth: p?.['stroke-width'],
+ zIndex: props.zIndex,
+ };
+ elements.push(
+ props.renderPolyline ? (
+ props.renderPolyline(polylineProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ break;
+ }
+ case 'MultiLineString': {
+ const p = feature.properties;
+ for (let i = 0; i < geometry.coordinates.length; i++) {
+ const polylineProps: PolylineProps = {
+ coordinates: toCoordinates(geometry.coordinates[i]!),
+ strokeColors: p?.stroke ? [p.stroke] : undefined,
+ strokeWidth: p?.['stroke-width'],
+ zIndex: props.zIndex,
+ };
+ const key = `${keyPrefix}-${i}`;
+ elements.push(
+ props.renderPolyline ? (
+ props.renderPolyline(polylineProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ }
+ break;
+ }
+ case 'Polygon': {
+ const outer = toCoordinates(geometry.coordinates[0]!);
+ const holes =
+ geometry.coordinates.length > 1
+ ? geometry.coordinates.slice(1).map(toCoordinates)
+ : undefined;
+ const p = feature.properties;
+ const polygonProps: PolygonProps = {
+ coordinates: outer,
+ holes,
+ fillColor: p?.fill,
+ strokeColor: p?.stroke,
+ strokeWidth: p?.['stroke-width'],
+ zIndex: props.zIndex,
+ };
+ elements.push(
+ props.renderPolygon ? (
+ props.renderPolygon(polygonProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ break;
+ }
+ case 'MultiPolygon': {
+ const p = feature.properties;
+ for (let i = 0; i < geometry.coordinates.length; i++) {
+ const rings = geometry.coordinates[i]!;
+ const outer = toCoordinates(rings[0]!);
+ const holes =
+ rings.length > 1 ? rings.slice(1).map(toCoordinates) : undefined;
+ const polygonProps: PolygonProps = {
+ coordinates: outer,
+ holes,
+ fillColor: p?.fill,
+ strokeColor: p?.stroke,
+ strokeWidth: p?.['stroke-width'],
+ zIndex: props.zIndex,
+ };
+ const key = `${keyPrefix}-${i}`;
+ elements.push(
+ props.renderPolygon ? (
+ props.renderPolygon(polygonProps, feature) ?? (
+
+ )
+ ) : (
+
+ )
+ );
+ }
+ break;
+ }
+ case 'GeometryCollection': {
+ for (let i = 0; i < geometry.geometries.length; i++) {
+ elements.push(
+ ...renderGeometry(
+ geometry.geometries[i]!,
+ feature,
+ props,
+ `${keyPrefix}-${i}`
+ )
+ );
+ }
+ break;
+ }
+ }
+
+ return elements;
+};
+
+export const GeoJson = (props: GeoJsonProps) => {
+ const { geojson } = props;
+
+ const elements = useMemo(() => {
+ const features = normalizeFeatures(geojson);
+ const result: ReactElement[] = [];
+
+ for (let i = 0; i < features.length; i++) {
+ const feature = features[i]!;
+ if (!feature.geometry) continue;
+
+ const key = feature.id != null ? String(feature.id) : String(i);
+ result.push(...renderGeometry(feature.geometry, feature, props, key));
+ }
+
+ return result;
+ }, [geojson, props]);
+
+ return <>{elements}>;
+};
diff --git a/src/components/GeoJson.types.ts b/src/components/GeoJson.types.ts
new file mode 100644
index 0000000..3bae1bd
--- /dev/null
+++ b/src/components/GeoJson.types.ts
@@ -0,0 +1,107 @@
+import type { ReactElement } from 'react';
+import type { MarkerProps } from './Marker.types';
+import type { PolygonProps } from './Polygon.types';
+import type { PolylineProps } from './Polyline.types';
+
+/**
+ * GeoJSON types per RFC 7946
+ * Note: GeoJSON positions use [longitude, latitude] order
+ */
+
+export type Position =
+ | [longitude: number, latitude: number]
+ | [longitude: number, latitude: number, altitude: number];
+
+export interface Point {
+ type: 'Point';
+ coordinates: Position;
+}
+
+export interface MultiPoint {
+ type: 'MultiPoint';
+ coordinates: Position[];
+}
+
+export interface LineString {
+ type: 'LineString';
+ coordinates: Position[];
+}
+
+export interface MultiLineString {
+ type: 'MultiLineString';
+ coordinates: Position[][];
+}
+
+export interface Polygon {
+ type: 'Polygon';
+ coordinates: Position[][];
+}
+
+export interface MultiPolygon {
+ type: 'MultiPolygon';
+ coordinates: Position[][][];
+}
+
+export interface GeometryCollection {
+ type: 'GeometryCollection';
+ geometries: Geometry[];
+}
+
+export type Geometry =
+ | Point
+ | MultiPoint
+ | LineString
+ | MultiLineString
+ | Polygon
+ | MultiPolygon
+ | GeometryCollection;
+
+export interface Feature {
+ type: 'Feature';
+ id?: string | number;
+ geometry: G;
+ properties: Record | null;
+}
+
+export interface FeatureCollection {
+ type: 'FeatureCollection';
+ features: Feature[];
+}
+
+export type GeoJSON = Geometry | Feature | FeatureCollection;
+
+export interface GeoJsonProps {
+ /**
+ * GeoJSON data to render.
+ * Accepts a `FeatureCollection`, `Feature`, or bare `Geometry`.
+ */
+ geojson: GeoJSON;
+ /**
+ * Z-index for all rendered features.
+ */
+ zIndex?: number;
+ /**
+ * Custom render function for `Point`/`MultiPoint` features.
+ * @param props - Default `MarkerProps` derived from the geometry.
+ * @param feature - The source GeoJSON `Feature`.
+ */
+ renderMarker?: (props: MarkerProps, feature: Feature) => ReactElement | null;
+ /**
+ * Custom render function for `LineString`/`MultiLineString` features.
+ * @param props - Default `PolylineProps` derived from the geometry.
+ * @param feature - The source GeoJSON `Feature`.
+ */
+ renderPolyline?: (
+ props: PolylineProps,
+ feature: Feature
+ ) => ReactElement | null;
+ /**
+ * Custom render function for `Polygon`/`MultiPolygon` features.
+ * @param props - Default `PolygonProps` derived from the geometry.
+ * @param feature - The source GeoJSON `Feature`.
+ */
+ renderPolygon?: (
+ props: PolygonProps,
+ feature: Feature
+ ) => ReactElement | null;
+}
diff --git a/src/components/Marker.tsx b/src/components/Marker.tsx
index 558cb19..0c11abd 100644
--- a/src/components/Marker.tsx
+++ b/src/components/Marker.tsx
@@ -1,80 +1,13 @@
import React from 'react';
-import type { ReactNode } from 'react';
-import { StyleSheet, type NativeSyntheticEvent } from 'react-native';
+import { StyleSheet } from 'react-native';
import LuggMarkerViewNativeComponent from '../fabric/LuggMarkerViewNativeComponent';
-import type { Coordinate, Point, PressEventPayload } from '../types';
+import type { MarkerProps } from './Marker.types';
-export type MarkerPressEvent = NativeSyntheticEvent;
-export type MarkerDragEvent = NativeSyntheticEvent;
-
-export interface MarkerProps {
- /**
- * Name used for debugging purposes
- */
- name?: string;
- /**
- * Marker position
- */
- coordinate: Coordinate;
- /**
- * Callout title
- */
- title?: string;
- /**
- * Callout description
- */
- description?: string;
- /**
- * Anchor point for custom marker views
- */
- anchor?: Point;
- /**
- * Z-index for marker ordering. Higher values render on top.
- */
- zIndex?: number;
- /**
- * Rotation angle in degrees clockwise from north.
- * @default 0
- */
- rotate?: number;
- /**
- * Scale factor for the marker.
- * @default 1
- */
- scale?: number;
- /**
- * Rasterize custom marker view to bitmap for better performance.
- * Set to false if you need live view updates (e.g., animations).
- * @platform ios, android
- * @default true
- */
- rasterize?: boolean;
- /**
- * Whether the marker can be dragged by the user.
- * @default false
- */
- draggable?: boolean;
- /**
- * Called when the marker is pressed
- */
- onPress?: (event: MarkerPressEvent) => void;
- /**
- * Called when marker drag starts
- */
- onDragStart?: (event: MarkerDragEvent) => void;
- /**
- * Called continuously as the marker is dragged
- */
- onDragChange?: (event: MarkerDragEvent) => void;
- /**
- * Called when marker drag ends
- */
- onDragEnd?: (event: MarkerDragEvent) => void;
- /**
- * Custom marker view
- */
- children?: ReactNode;
-}
+export type {
+ MarkerProps,
+ MarkerPressEvent,
+ MarkerDragEvent,
+} from './Marker.types';
export class Marker extends React.PureComponent {
private getStyle(zIndex: number | undefined) {
diff --git a/src/components/Marker.types.ts b/src/components/Marker.types.ts
new file mode 100644
index 0000000..ff40ee5
--- /dev/null
+++ b/src/components/Marker.types.ts
@@ -0,0 +1,75 @@
+import type { ReactNode } from 'react';
+import type { NativeSyntheticEvent } from 'react-native';
+import type { Coordinate, Point, PressEventPayload } from '../types';
+
+export type MarkerPressEvent = NativeSyntheticEvent;
+export type MarkerDragEvent = NativeSyntheticEvent;
+
+export interface MarkerProps {
+ /**
+ * Name used for debugging purposes
+ */
+ name?: string;
+ /**
+ * Marker position
+ */
+ coordinate: Coordinate;
+ /**
+ * Callout title
+ */
+ title?: string;
+ /**
+ * Callout description
+ */
+ description?: string;
+ /**
+ * Anchor point for custom marker views
+ */
+ anchor?: Point;
+ /**
+ * Z-index for marker ordering. Higher values render on top.
+ */
+ zIndex?: number;
+ /**
+ * Rotation angle in degrees clockwise from north.
+ * @default 0
+ */
+ rotate?: number;
+ /**
+ * Scale factor for the marker.
+ * @default 1
+ */
+ scale?: number;
+ /**
+ * Rasterize custom marker view to bitmap for better performance.
+ * Set to false if you need live view updates (e.g., animations).
+ * @platform ios, android
+ * @default true
+ */
+ rasterize?: boolean;
+ /**
+ * Whether the marker can be dragged by the user.
+ * @default false
+ */
+ draggable?: boolean;
+ /**
+ * Called when the marker is pressed
+ */
+ onPress?: (event: MarkerPressEvent) => void;
+ /**
+ * Called when marker drag starts
+ */
+ onDragStart?: (event: MarkerDragEvent) => void;
+ /**
+ * Called continuously as the marker is dragged
+ */
+ onDragChange?: (event: MarkerDragEvent) => void;
+ /**
+ * Called when marker drag ends
+ */
+ onDragEnd?: (event: MarkerDragEvent) => void;
+ /**
+ * Custom marker view
+ */
+ children?: ReactNode;
+}
diff --git a/src/components/Marker.web.tsx b/src/components/Marker.web.tsx
index a4ab1a4..3cc6bc0 100644
--- a/src/components/Marker.web.tsx
+++ b/src/components/Marker.web.tsx
@@ -1,31 +1,28 @@
import { useCallback, useEffect, useRef } from 'react';
import { AdvancedMarker } from '@vis.gl/react-google-maps';
import { useMapContext } from '../MapProvider.web';
-import type { MarkerProps } from './Marker';
+import type { MarkerProps } from './Marker.types';
const toWebAnchor = (value: number) => `-${value * 100}%`;
-function createEvent(
+const createEvent = (
e: google.maps.MapMouseEvent,
coordinate: MarkerProps['coordinate']
-) {
- const latLng = e.latLng;
- const domEvent = e.domEvent as MouseEvent;
- return {
+) =>
+ ({
nativeEvent: {
coordinate: {
- latitude: latLng?.lat() ?? coordinate.latitude,
- longitude: latLng?.lng() ?? coordinate.longitude,
+ latitude: e.latLng?.lat() ?? coordinate.latitude,
+ longitude: e.latLng?.lng() ?? coordinate.longitude,
},
point: {
- x: domEvent?.clientX ?? 0,
- y: domEvent?.clientY ?? 0,
+ x: (e.domEvent as MouseEvent)?.clientX ?? 0,
+ y: (e.domEvent as MouseEvent)?.clientY ?? 0,
},
},
- } as any;
-}
+ } as any);
-export function Marker({
+export const Marker = ({
coordinate,
title,
anchor,
@@ -38,7 +35,7 @@ export function Marker({
onDragChange,
onDragEnd,
children,
-}: MarkerProps) {
+}: MarkerProps) => {
const { moveCamera } = useMapContext();
const dragPositionRef = useRef(null);
@@ -120,4 +117,4 @@ export function Marker({
{children}
);
-}
+};
diff --git a/src/components/Polygon.tsx b/src/components/Polygon.tsx
index 30ff29c..d934082 100644
--- a/src/components/Polygon.tsx
+++ b/src/components/Polygon.tsx
@@ -1,39 +1,9 @@
import React from 'react';
-import type { ColorValue } from 'react-native';
import { StyleSheet } from 'react-native';
import LuggPolygonViewNativeComponent from '../fabric/LuggPolygonViewNativeComponent';
-import type { Coordinate } from '../types';
+import type { PolygonProps } from './Polygon.types';
-export interface PolygonProps {
- /**
- * Array of coordinates forming the polygon boundary
- */
- coordinates: Coordinate[];
- /**
- * Array of coordinate arrays representing interior holes
- */
- holes?: Coordinate[][];
- /**
- * Stroke (outline) color
- */
- strokeColor?: ColorValue;
- /**
- * Stroke width in points
- */
- strokeWidth?: number;
- /**
- * Fill color of the polygon
- */
- fillColor?: ColorValue;
- /**
- * Z-index for layering
- */
- zIndex?: number;
- /**
- * Called when the polygon is tapped
- */
- onPress?: () => void;
-}
+export type { PolygonProps } from './Polygon.types';
export class Polygon extends React.PureComponent {
private _cachedZIndex: number | undefined;
diff --git a/src/components/Polygon.types.ts b/src/components/Polygon.types.ts
new file mode 100644
index 0000000..0fd4cab
--- /dev/null
+++ b/src/components/Polygon.types.ts
@@ -0,0 +1,33 @@
+import type { ColorValue } from 'react-native';
+import type { Coordinate } from '../types';
+
+export interface PolygonProps {
+ /**
+ * Array of coordinates forming the polygon boundary
+ */
+ coordinates: Coordinate[];
+ /**
+ * Array of coordinate arrays representing interior holes
+ */
+ holes?: Coordinate[][];
+ /**
+ * Stroke (outline) color
+ */
+ strokeColor?: ColorValue;
+ /**
+ * Stroke width in points
+ */
+ strokeWidth?: number;
+ /**
+ * Fill color of the polygon
+ */
+ fillColor?: ColorValue;
+ /**
+ * Z-index for layering
+ */
+ zIndex?: number;
+ /**
+ * Called when the polygon is tapped
+ */
+ onPress?: () => void;
+}
diff --git a/src/components/Polygon.web.tsx b/src/components/Polygon.web.tsx
index bf596fc..7651037 100644
--- a/src/components/Polygon.web.tsx
+++ b/src/components/Polygon.web.tsx
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useRef } from 'react';
import { useMapContext } from '../MapProvider.web';
-import type { PolygonProps } from './Polygon';
+import type { PolygonProps } from './Polygon.types';
-export function Polygon({
+export const Polygon = ({
coordinates,
holes,
strokeColor = '#000000',
@@ -10,7 +10,7 @@ export function Polygon({
fillColor = 'rgba(0, 0, 0, 0.3)',
zIndex = 0,
onPress,
-}: PolygonProps) {
+}: PolygonProps) => {
const { map } = useMapContext();
const polygonRef = useRef(null);
const listenersRef = useRef([]);
@@ -19,24 +19,6 @@ export function Polygon({
onPress?.();
}, [onPress]);
- const applyHighlight = useCallback(() => {
- const polygon = polygonRef.current;
- if (!polygon) return;
- polygon.setOptions({
- fillOpacity: 0.5,
- strokeOpacity: 0.5,
- });
- }, []);
-
- const restoreHighlight = useCallback(() => {
- const polygon = polygonRef.current;
- if (!polygon) return;
- polygon.setOptions({
- fillOpacity: 1,
- strokeOpacity: 1,
- });
- }, []);
-
// Cleanup on unmount
useEffect(() => {
return () => {
@@ -56,15 +38,10 @@ export function Polygon({
listenersRef.current = [];
if (onPress) {
- listenersRef.current.push(
- polygon.addListener('click', handleClick),
- polygon.addListener('mousedown', applyHighlight),
- polygon.addListener('mouseup', restoreHighlight),
- polygon.addListener('mouseout', restoreHighlight)
- );
+ listenersRef.current.push(polygon.addListener('click', handleClick));
}
polygon.set('clickable', !!onPress);
- }, [onPress, handleClick, applyHighlight, restoreHighlight]);
+ }, [onPress, handleClick]);
// Sync polygon with props
useEffect(() => {
@@ -108,12 +85,7 @@ export function Polygon({
polygonRef.current = polygon;
if (onPress) {
- listenersRef.current.push(
- polygon.addListener('click', handleClick),
- polygon.addListener('mousedown', applyHighlight),
- polygon.addListener('mouseup', restoreHighlight),
- polygon.addListener('mouseout', restoreHighlight)
- );
+ listenersRef.current.push(polygon.addListener('click', handleClick));
}
}
}, [
@@ -126,9 +98,7 @@ export function Polygon({
zIndex,
onPress,
handleClick,
- applyHighlight,
- restoreHighlight,
]);
return null;
-}
+};
diff --git a/src/components/Polyline.tsx b/src/components/Polyline.tsx
index e5b2680..94533f6 100644
--- a/src/components/Polyline.tsx
+++ b/src/components/Polyline.tsx
@@ -1,61 +1,13 @@
import React from 'react';
-import type { ColorValue } from 'react-native';
import { StyleSheet } from 'react-native';
import LuggPolylineViewNativeComponent from '../fabric/LuggPolylineViewNativeComponent';
-import type { Coordinate } from '../types';
+import type { PolylineProps } from './Polyline.types';
-export type PolylineEasing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
-
-export interface PolylineAnimatedOptions {
- /**
- * Animation duration in milliseconds
- * @default 2150
- */
- duration?: number;
- /**
- * Easing function for the animation
- * @default 'linear'
- */
- easing?: PolylineEasing;
- /**
- * Portion of the line visible as trail (0-1)
- * 1.0 = full snake effect, 0.2 = short worm
- * @default 1.0
- */
- trailLength?: number;
- /**
- * Delay before animation starts in milliseconds
- * @default 0
- */
- delay?: number;
-}
-
-export interface PolylineProps {
- /**
- * Array of coordinates forming the polyline
- */
- coordinates: Coordinate[];
- /**
- * Gradient colors along the polyline
- */
- strokeColors?: ColorValue[];
- /**
- * Line width in points
- */
- strokeWidth?: number;
- /**
- * Animate the polyline with a snake effect
- */
- animated?: boolean;
- /**
- * Animation configuration options
- */
- animatedOptions?: PolylineAnimatedOptions;
- /**
- * Z-index for layering polylines
- */
- zIndex?: number;
-}
+export type {
+ PolylineProps,
+ PolylineEasing,
+ PolylineAnimatedOptions,
+} from './Polyline.types';
export class Polyline extends React.PureComponent {
private getStyle(zIndex: number | undefined) {
diff --git a/src/components/Polyline.types.ts b/src/components/Polyline.types.ts
new file mode 100644
index 0000000..0dbff8a
--- /dev/null
+++ b/src/components/Polyline.types.ts
@@ -0,0 +1,55 @@
+import type { ColorValue } from 'react-native';
+import type { Coordinate } from '../types';
+
+export type PolylineEasing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
+
+export interface PolylineAnimatedOptions {
+ /**
+ * Animation duration in milliseconds
+ * @default 2150
+ */
+ duration?: number;
+ /**
+ * Easing function for the animation
+ * @default 'linear'
+ */
+ easing?: PolylineEasing;
+ /**
+ * Portion of the line visible as trail (0-1)
+ * 1.0 = full snake effect, 0.2 = short worm
+ * @default 1.0
+ */
+ trailLength?: number;
+ /**
+ * Delay before animation starts in milliseconds
+ * @default 0
+ */
+ delay?: number;
+}
+
+export interface PolylineProps {
+ /**
+ * Array of coordinates forming the polyline
+ */
+ coordinates: Coordinate[];
+ /**
+ * Gradient colors along the polyline
+ */
+ strokeColors?: ColorValue[];
+ /**
+ * Line width in points
+ */
+ strokeWidth?: number;
+ /**
+ * Animate the polyline with a snake effect
+ */
+ animated?: boolean;
+ /**
+ * Animation configuration options
+ */
+ animatedOptions?: PolylineAnimatedOptions;
+ /**
+ * Z-index for layering polylines
+ */
+ zIndex?: number;
+}
diff --git a/src/components/Polyline.web.tsx b/src/components/Polyline.web.tsx
index cb6772f..a64c187 100644
--- a/src/components/Polyline.web.tsx
+++ b/src/components/Polyline.web.tsx
@@ -1,10 +1,10 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useMapContext } from '../MapProvider.web';
-import type { PolylineProps, PolylineEasing } from './Polyline';
+import type { PolylineProps, PolylineEasing } from './Polyline.types';
const DEFAULT_DURATION = 2150;
-function applyEasing(t: number, easing: PolylineEasing = 'linear'): number {
+const applyEasing = (t: number, easing: PolylineEasing = 'linear'): number => {
switch (easing) {
case 'easeIn':
return t * t;
@@ -15,9 +15,13 @@ function applyEasing(t: number, easing: PolylineEasing = 'linear'): number {
default:
return t;
}
-}
+};
-function interpolateColor(color1: string, color2: string, t: number): string {
+const interpolateColor = (
+ color1: string,
+ color2: string,
+ t: number
+): string => {
const hex = (c: string) => parseInt(c, 16);
const r1 = hex(color1.slice(1, 3));
const g1 = hex(color1.slice(3, 5));
@@ -33,9 +37,9 @@ function interpolateColor(color1: string, color2: string, t: number): string {
return `#${r.toString(16).padStart(2, '0')}${g
.toString(16)
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
-}
+};
-function getGradientColor(colors: string[], position: number): string {
+const getGradientColor = (colors: string[], position: number): string => {
if (colors.length === 0) return '#000000';
if (colors.length === 1 || position <= 0) return colors[0]!;
if (position >= 1) return colors[colors.length - 1]!;
@@ -45,16 +49,16 @@ function getGradientColor(colors: string[], position: number): string {
const t = scaledPos - index;
return interpolateColor(colors[index]!, colors[index + 1]!, t);
-}
+};
-export function Polyline({
+export const Polyline = ({
coordinates,
strokeColors,
strokeWidth = 1,
animated,
animatedOptions,
zIndex,
-}: PolylineProps) {
+}: PolylineProps) => {
const resolvedZIndex = zIndex ?? (animated ? 1 : 0);
const { map, isDragging } = useMapContext();
@@ -313,4 +317,4 @@ export function Polyline({
]);
return null;
-}
+};
diff --git a/src/components/index.ts b/src/components/index.ts
index 276c24b..64e062e 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,10 +1,16 @@
export { Marker } from './Marker';
-export type { MarkerProps, MarkerPressEvent, MarkerDragEvent } from './Marker';
+export type {
+ MarkerProps,
+ MarkerPressEvent,
+ MarkerDragEvent,
+} from './Marker.types';
export { Polygon } from './Polygon';
-export type { PolygonProps } from './Polygon';
+export type { PolygonProps } from './Polygon.types';
export { Polyline } from './Polyline';
export type {
PolylineProps,
PolylineEasing,
PolylineAnimatedOptions,
-} from './Polyline';
+} from './Polyline.types';
+export { GeoJson } from './GeoJson';
+export type { GeoJsonProps } from './GeoJson.types';
diff --git a/src/components/index.web.ts b/src/components/index.web.ts
index 8583c34..b680ca9 100644
--- a/src/components/index.web.ts
+++ b/src/components/index.web.ts
@@ -1,6 +1,8 @@
export { Marker } from './Marker.web';
export { Polygon } from './Polygon.web';
export { Polyline } from './Polyline.web';
-export type { MarkerProps } from './Marker';
-export type { PolygonProps } from './Polygon';
-export type { PolylineProps } from './Polyline';
+export type { MarkerProps } from './Marker.types';
+export type { PolygonProps } from './Polygon.types';
+export type { PolylineProps } from './Polyline.types';
+export { GeoJson } from './GeoJson';
+export type { GeoJsonProps } from './GeoJson.types';
diff --git a/src/index.ts b/src/index.ts
index 8781e5c..6f899e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,6 +15,8 @@ export type {
PolylineEasing,
PolylineAnimatedOptions,
} from './components';
+export { GeoJson } from './components';
+export type { GeoJsonProps } from './components';
export type {
MapViewProps,
MapViewRef,
@@ -33,3 +35,10 @@ export type {
EdgeInsets,
PressEventPayload,
} from './types';
+export type {
+ GeoJSON,
+ Feature,
+ FeatureCollection,
+ Geometry,
+ Position,
+} from './components/GeoJson.types';
diff --git a/src/index.web.ts b/src/index.web.ts
index d6e87bb..54ecf38 100644
--- a/src/index.web.ts
+++ b/src/index.web.ts
@@ -7,6 +7,8 @@ export { Polygon } from './components/index.web';
export type { PolygonProps } from './components/index.web';
export { Polyline } from './components/index.web';
export type { PolylineProps } from './components/index.web';
+export { GeoJson } from './components/index.web';
+export type { GeoJsonProps } from './components/index.web';
export type {
MapViewProps,
MapViewRef,
@@ -20,3 +22,10 @@ export type {
Point,
EdgeInsets,
} from './types';
+export type {
+ GeoJSON,
+ Feature,
+ FeatureCollection,
+ Geometry,
+ Position,
+} from './components/GeoJson.types';