├── .gitignore
├── .npmignore
├── .vscode
└── settings.json
├── HTML
├── .gitignore
├── gulpfile.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── readme.md
├── src
│ ├── MapComponent.test.tsx
│ ├── MapComponent.view.test.tsx
│ ├── MapLayers.test.tsx
│ ├── MapLayers.tsx
│ ├── MapMarkers.tsx
│ ├── MapShapes.tsx
│ ├── __snapshots__
│ │ ├── MapComponent.test.tsx.snap
│ │ ├── MapComponent.view.test.tsx.snap
│ │ └── MapLayers.test.tsx.snap
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ ├── mapComponent.tsx
│ ├── mapComponent.view.tsx
│ ├── models.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── setupTests.ts
│ ├── styles
│ │ ├── markerAnimations.css
│ │ └── markers.css
│ ├── testData
│ │ ├── mockMapLayers.ts
│ │ ├── mockMapMarkers.ts
│ │ ├── mockMapShapes.ts
│ │ └── svgIcons.ts
│ ├── utilities.test.ts.old
│ ├── utilities.ts
│ └── webBase64Image.ts
├── tsconfig.json
├── yarn-error.log
└── yarn.lock
├── WebViewLeaflet
├── ActivityOverlay.tsx
├── DebugMessageBox.tsx
├── WebViewLeaflet.tsx
├── WebViewLeaflet.view.tsx
├── assets
│ └── index.html
├── index.d.ts
├── index.ts
├── models.ts
├── package.json
├── yarn-error.log
└── yarn.lock
├── assets
├── icon.png
└── splash.png
├── demoApp
├── .expo-shared
│ └── assets.json
├── .gitignore
├── App.tsx
├── app.json
├── assets
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── gulpfile.js
├── package.json
├── tsconfig.json
├── yarn-error.log
└── yarn.lock
├── package.json
├── readme.md
├── secrets.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 | HTML/dist/*
13 | HTML/cache/*
14 | HTML/build/*
15 | HTML/.cache/*
16 | HTML/node_modules
17 | WebViewLeaflet/dist/*
18 | WebViewLeaflet/cache/*
19 | WebViewLeaflet/build/*
20 | WebViewLeaflet/.cache/*
21 | WebViewLeaflet/node_modules
22 | secrets.js
23 | secrets.ts
24 | /HTML/precompile
25 | /HTML/readyForBuild
26 | /HTML/.cache
27 | node_modules
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | ..\..\..\..\c:\Users\regin\Dropbox\react-native-webview-leaflet-5\html\build\index.html
2 | ..\..\..\..\c:\Users\regin\Dropbox\react-native-webview-leaflet-5\html\src\testData
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/HTML/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/HTML/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require("gulp");
2 | const inlinesource = require("gulp-inline-source");
3 | const replace = require("gulp-replace");
4 | const clean = require("gulp-clean");
5 | const rename = require("gulp-rename");
6 |
7 | const REACT_BUILD_DIRECTORY = "build";
8 | const REACT_BUILD_FILES = "./build/*.html";
9 | const FILE_NAME_AFTER_ADDING_INLINE_TAGS = "indexWithTags.html";
10 | const DIST_HTML_FILE_NAME = "index.html";
11 | const DIST_DIRECTORY = "dist";
12 |
13 | gulp.task("clean", function() {
14 | return gulp
15 | .src(
16 | [
17 | `${REACT_BUILD_DIRECTORY}/${FILE_NAME_AFTER_ADDING_INLINE_TAGS}`,
18 | DIST_DIRECTORY
19 | ],
20 | {
21 | allowEmpty: true,
22 | read: false
23 | }
24 | )
25 | .pipe(clean());
26 | });
27 |
28 | gulp.task("disableBrowserTestFlag", () => {
29 | return gulp
30 | .src(["./src/MapComponent.tsx"])
31 | .pipe(
32 | replace(
33 | "const ENABLE_BROWSER_TESTING = true;",
34 | "const ENABLE_BROWSER_TESTING = false;"
35 | )
36 | )
37 | .pipe(gulp.dest("./src"));
38 | });
39 |
40 | gulp.task("enableBrowserTestFlag", () => {
41 | return gulp
42 | .src(["./src/MapComponent.tsx"])
43 | .pipe(
44 | replace(
45 | "const ENABLE_BROWSER_TESTING = false;",
46 | "const ENABLE_BROWSER_TESTING = true;"
47 | )
48 | )
49 | .pipe(gulp.dest("./src"));
50 | });
51 |
52 | gulp.task("addInlineTags", function() {
53 | return gulp
54 | .src(REACT_BUILD_FILES)
55 | .pipe(replace('rel="stylesheet"', 'rel="stylesheet" inline'))
56 | .pipe(replace(">", " inline>"))
57 | .pipe(rename(FILE_NAME_AFTER_ADDING_INLINE_TAGS))
58 | .pipe(gulp.dest(REACT_BUILD_DIRECTORY));
59 | });
60 |
61 | gulp.task("inlineSource", function() {
62 | return gulp
63 | .src(`./${REACT_BUILD_DIRECTORY}/${FILE_NAME_AFTER_ADDING_INLINE_TAGS}`)
64 | .pipe(inlinesource())
65 | .pipe(rename(DIST_HTML_FILE_NAME))
66 | .pipe(gulp.dest(DIST_DIRECTORY));
67 | });
68 |
69 | exports.build = gulp.series("clean", "addInlineTags", "inlineSource");
70 |
--------------------------------------------------------------------------------
/HTML/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "html",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/jest": "^24.0.0",
10 | "@types/node": "^12.0.0",
11 | "@types/react": "^16.9.0",
12 | "@types/react-dom": "^16.9.0",
13 | "@types/react-leaflet": "^2.5.0",
14 | "@types/react-leaflet-markercluster": "^2.0.0",
15 | "@types/react-measure": "^2.0.5",
16 | "leaflet": "^1.6.0",
17 | "leaflet.markercluster": "^1.4.1",
18 | "react": "^16.12.0",
19 | "react-dom": "^16.12.0",
20 | "react-leaflet": "^2.6.1",
21 | "react-leaflet-markercluster": "^2.0.0-rc3",
22 | "react-measure": "^2.3.0",
23 | "react-scripts": "3.3.0",
24 | "typescript": "~3.7.2"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject",
31 | "cleanAssets": "del-cli --force ../WebViewLeaflet/assets/**/*",
32 | "copyDist": "npx copyfiles -u 1 ./dist/index.html ../WebViewLeaflet/assets",
33 | "dist": "gulp disableBrowserTestFlag && yarn build && gulp build && yarn cleanAssets && yarn copyDist && gulp enableBrowserTestFlag",
34 | "cpx": "cpx '../WebViewLeaflet/models.ts' ./src --watch"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "devDependencies": {
52 | "del-cli": "^3.0.0",
53 | "gulp": "^4.0.2",
54 | "gulp-clean": "^0.4.0",
55 | "gulp-inline-source": "^4.0.0",
56 | "gulp-rename": "^2.0.0",
57 | "gulp-replace": "^1.0.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/HTML/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reggie3/react-native-webview-leaflet/a27bc89559bc8ab470ae89b0d4faef1a2433ebb0/HTML/public/favicon.ico
--------------------------------------------------------------------------------
/HTML/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/HTML/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/HTML/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/HTML/readme.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/HTML/src/MapComponent.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "@testing-library/react";
3 | import MapComponent from "./MapComponent";
4 |
5 | describe("MapComponent", () => {
6 | test("it renders", () => {
7 | const { asFragment } = render();
8 | expect(asFragment()).toMatchSnapshot();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/HTML/src/MapComponent.view.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "@testing-library/react";
3 | import MapComponentView from "./MapComponent.view";
4 |
5 | describe("MapComponentView", () => {
6 | test("it renders", () => {
7 | const { asFragment } = render(
8 | {}}
10 | debugMessages={[]}
11 | mapCenterPosition={[36.56, -76.17]}
12 | mapLayers={[]}
13 | mapMarkers={[]}
14 | onMapEvent={() => {}}
15 | setMapRef={() => {}}
16 | zoom={13}
17 | />
18 | );
19 | expect(asFragment()).toMatchSnapshot();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/HTML/src/MapLayers.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { default as MapLayerComponent } from "./MapLayers";
3 | import { render } from "@testing-library/react";
4 | import mockMapLayers from "./testData/mockMapLayers";
5 | import { Map } from "react-leaflet";
6 |
7 | describe("MapLayers Component", () => {
8 | test("it renders", () => {
9 | console.log(MapLayerComponent);
10 | const { asFragment } = render(
11 |
14 | );
15 | expect(asFragment()).toMatchSnapshot();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/HTML/src/MapLayers.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | TileLayer,
4 | LayersControl,
5 | WMSTileLayer,
6 | WMSTileLayerProps,
7 | TileLayerProps,
8 | ImageOverlay,
9 | ImageOverlayProps
10 | } from "react-leaflet";
11 | import { MapLayer, MapLayerType } from "./models";
12 |
13 | const { BaseLayer } = LayersControl;
14 |
15 | interface MapLayersProps {
16 | mapLayers: MapLayer[];
17 | }
18 |
19 | class MapLayers extends React.Component {
20 | private Layer = (props: MapLayer): JSX.Element => {
21 | switch (props.layerType) {
22 | case MapLayerType.IMAGE_LAYER:
23 | return ;
24 | case MapLayerType.WMS_TILE_LAYER:
25 | return ;
26 | default:
27 | return ;
28 | }
29 | };
30 |
31 | private Layers = (): JSX.Element[] => {
32 | const { mapLayers } = this.props;
33 | return mapLayers.map(
34 | (layer: MapLayer, index: number): JSX.Element => {
35 | if (layer.baseLayerName && mapLayers.length > 1) {
36 | return (
37 |
42 |
43 |
44 | );
45 | }
46 | return ;
47 | }
48 | );
49 | };
50 |
51 | render() {
52 | const { mapLayers } = this.props;
53 | if (mapLayers.length > 1) {
54 | return {this.Layers()};
55 | } else {
56 | return <>{this.Layers()}>;
57 | }
58 | }
59 | }
60 |
61 | export default MapLayers;
62 |
--------------------------------------------------------------------------------
/HTML/src/MapMarkers.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { LayerGroup, Marker, Popup } from "react-leaflet";
3 | import MarkerClusterGroup from "react-leaflet-markercluster";
4 | import { createDivIcon } from "./utilities";
5 | import {
6 | WebViewLeafletEvents,
7 | MapMarker,
8 | OWN_POSTION_MARKER_ID
9 | } from "./models";
10 | import { LatLngExpression } from "leaflet";
11 | require("react-leaflet-markercluster/dist/styles.min.css");
12 |
13 | interface MapMarkersProps {
14 | mapMarkers: MapMarker[];
15 | onMapEvent: (mapEvent: WebViewLeafletEvents, payload: any) => void;
16 | useMarkerClustering?: boolean;
17 | }
18 |
19 | export default class MapMarkers extends React.Component {
20 | private MapMarker = ({ mapMarker }: { mapMarker: MapMarker }) => {
21 | return (
22 | {
27 | this.props.onMapEvent(WebViewLeafletEvents.ON_MAP_MARKER_CLICKED, {
28 | mapMarkerID: mapMarker.id
29 | });
30 | }}
31 | >
32 | {mapMarker.title && {mapMarker.title}}
33 |
34 | );
35 | };
36 |
37 | render() {
38 | const { mapMarkers, useMarkerClustering = true } = this.props;
39 | if (useMarkerClustering) {
40 | return (
41 |
42 |
43 | {mapMarkers.map((mapMarker: MapMarker) => {
44 | if (mapMarker.id !== OWN_POSTION_MARKER_ID) {
45 | return (
46 |
50 | );
51 | } else {
52 | return null;
53 | }
54 | })}
55 |
56 | {mapMarkers.map((mapMarker: MapMarker) => {
57 | if (mapMarker.id === OWN_POSTION_MARKER_ID) {
58 | return ;
59 | } else {
60 | return null;
61 | }
62 | })}
63 |
64 | );
65 | } else {
66 | return (
67 |
68 | {mapMarkers.map((mapMarker: MapMarker) => {
69 | return ;
70 | })}
71 |
72 | );
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/HTML/src/MapShapes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | Circle,
4 | Polygon,
5 | CircleMarker,
6 | Polyline,
7 | Rectangle,
8 | CircleMarkerProps,
9 | PolylineProps,
10 | PolygonProps,
11 | RectangleProps,
12 | CircleProps
13 | } from "react-leaflet";
14 | import { MapShapeType, MapShape, WebViewLeafletEvents } from "./models";
15 |
16 | export interface MapMapShapesProps {
17 | mapShapes: MapShape[];
18 | onMapEvent: (mapEvent: WebViewLeafletEvents, payload: any) => void;
19 | }
20 |
21 | class MapShapes extends React.Component {
22 | private Shape = (props: any) => {
23 | switch (props.shapeType) {
24 | case MapShapeType.CIRCLE:
25 | return ;
26 | case MapShapeType.CIRCLE_MARKER: {
27 | return ;
28 | }
29 | case MapShapeType.POLYGON: {
30 | return ;
31 | }
32 | case MapShapeType.POLYLINE: {
33 | return ;
34 | }
35 | case MapShapeType.RECTANGLE: {
36 | return ;
37 | }
38 | default:
39 | console.warn("Unknown map shape type", props.shapeType);
40 | return null;
41 | }
42 | };
43 |
44 | render() {
45 | return (
46 | <>
47 | {this.props.mapShapes.map(mapShape => {
48 | const props = { ...mapShape, color: mapShape.color ?? "white" };
49 | return ;
50 | })}
51 | >
52 | );
53 | }
54 | }
55 |
56 | export default MapShapes;
57 |
--------------------------------------------------------------------------------
/HTML/src/__snapshots__/MapComponent.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MapComponent it renders 1`] = `
4 |
5 |
9 |
10 | `;
11 |
--------------------------------------------------------------------------------
/HTML/src/__snapshots__/MapComponent.view.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MapComponentView it renders 1`] = `
4 |
5 |
9 |
10 | `;
11 |
--------------------------------------------------------------------------------
/HTML/src/__snapshots__/MapLayers.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MapLayers Component it renders 1`] = `
4 |
5 |
10 |
14 |
56 |
59 |
62 |
65 |
68 |
71 |
72 |
75 |
101 |
155 |
158 |
179 |
180 |
181 |
182 | `;
183 |
--------------------------------------------------------------------------------
/HTML/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/HTML/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./MapComponent";
5 | import * as serviceWorker from "./serviceWorker";
6 |
7 | ReactDOM.render(, document.getElementById("root"));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/HTML/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/HTML/src/mapComponent.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import "leaflet/dist/leaflet.css";
3 | import "leaflet/dist/images/layers-2x.png";
4 | import "leaflet/dist/images/layers.png";
5 | import "leaflet/dist/images/marker-icon-2x.png";
6 | import icon from "leaflet/dist/images/marker-icon.png";
7 | import iconShadow from "leaflet/dist/images/marker-shadow.png";
8 | import MapComponentView from "./MapComponent.view";
9 | import L from "leaflet";
10 | import mockMapLayers from "./testData/mockMapLayers";
11 | import mockMapShapes from "./testData/mockMapShapes";
12 | import mockMapMarkers from "./testData/mockMapMarkers";
13 | import {
14 | WebViewLeafletEvents,
15 | MapEventMessage,
16 | MapLayer,
17 | MapMarker,
18 | MapShape,
19 | INFINITE_ANIMATION_ITERATIONS,
20 | AnimationType,
21 | WebviewLeafletMessagePayload
22 | } from "./models";
23 | import "./styles/markers.css";
24 | import "./styles/markerAnimations.css";
25 | import { LatLng } from "react-leaflet";
26 |
27 | export const SHOW_DEBUG_INFORMATION = false;
28 | const ENABLE_BROWSER_TESTING = true;
29 |
30 | interface State {
31 | debugMessages: string[];
32 | isFromNative: boolean;
33 | isMobile: boolean;
34 | mapCenterPosition: LatLng;
35 | mapLayers: MapLayer[];
36 | mapMarkers: MapMarker[];
37 | mapShapes: MapShape[];
38 | ownPositionMarker: MapMarker;
39 | mapRef: any;
40 | zoom: number;
41 | }
42 |
43 | export default class MapComponent extends Component<{}, State> {
44 | constructor(props: {}) {
45 | super(props);
46 | this.state = {
47 | debugMessages: ["test"],
48 | isFromNative: false,
49 | isMobile: null,
50 | mapCenterPosition: { lat: 36.56, lng: -76.17 },
51 | mapLayers: [],
52 | mapMarkers: [],
53 | mapShapes: [],
54 | mapRef: null,
55 | ownPositionMarker: null,
56 | zoom: 6
57 | };
58 | }
59 |
60 | componentDidMount = () => {
61 | let DefaultIcon = L.icon({
62 | iconUrl: icon,
63 | shadowUrl: iconShadow
64 | });
65 | L.Marker.prototype.options.icon = DefaultIcon;
66 |
67 | this.addEventListeners();
68 | this.sendMessage({
69 | msg: WebViewLeafletEvents.MAP_COMPONENT_MOUNTED
70 | });
71 | if (ENABLE_BROWSER_TESTING) {
72 | this.loadMockData();
73 | }
74 | };
75 |
76 | componentDidUpdate = (prevProps: any, prevState: State) => {
77 | const { mapRef } = this.state;
78 | if (mapRef && !prevState.mapRef) {
79 | mapRef.current?.leafletElement.invalidateSize();
80 | this.sendMessage({
81 | msg: WebViewLeafletEvents.MAP_READY
82 | });
83 | }
84 | };
85 |
86 | private addDebugMessage = (msg: any) => {
87 | if (typeof msg === "object") {
88 | this.addDebugMessage("STRINGIFIED");
89 | this.setState({
90 | debugMessages: [
91 | ...this.state.debugMessages,
92 | JSON.stringify(msg, null, 4)
93 | ]
94 | });
95 | } else {
96 | this.setState({ debugMessages: [...this.state.debugMessages, msg] });
97 | }
98 | };
99 |
100 | private addEventListeners = () => {
101 | if (document) {
102 | document.addEventListener("message", this.handleMessage);
103 | this.addDebugMessage("set document listeners");
104 | this.sendMessage({
105 | msg: WebViewLeafletEvents.DOCUMENT_EVENT_LISTENER_ADDED
106 | });
107 | }
108 | if (window) {
109 | window.addEventListener("message", this.handleMessage);
110 | this.addDebugMessage("setting Window");
111 | this.sendMessage({
112 | msg: WebViewLeafletEvents.WINDOW_EVENT_LISTENER_ADDED
113 | });
114 | }
115 | if (!document && !window) {
116 | this.sendMessage({
117 | error: WebViewLeafletEvents.UNABLE_TO_ADD_EVENT_LISTENER
118 | });
119 | return;
120 | }
121 | };
122 |
123 | private handleMessage = (event: any & { data: State }) => {
124 | this.addDebugMessage(event.data);
125 | try {
126 | if (event.data.mapCenterPosition) {
127 | this.state.mapRef.leafletElement.flyTo([
128 | event.data.mapCenterPosition.lat,
129 | event.data.mapCenterPosition.lng
130 | ]);
131 | }
132 | this.setState({ ...this.state, ...event.data });
133 | } catch (error) {
134 | this.addDebugMessage({ error: JSON.stringify(error) });
135 | }
136 | };
137 |
138 | protected sendMessage = (message: MapEventMessage) => {
139 | // @ts-ignore
140 | if (window.ReactNativeWebView) {
141 | // @ts-ignore
142 | window.ReactNativeWebView.postMessage(JSON.stringify(message));
143 | console.log("sendMessage ", JSON.stringify(message));
144 | }
145 | };
146 |
147 | private loadMockData = () => {
148 | this.addDebugMessage("loading mock data");
149 | this.setState({
150 | mapLayers: mockMapLayers,
151 | mapMarkers: mockMapMarkers,
152 | mapShapes: mockMapShapes,
153 | ownPositionMarker: {
154 | id: "Own Position",
155 | position: { lat: 36.56, lng: -76.17 },
156 | icon: "❤️",
157 | size: [32, 32],
158 | animation: {
159 | duration: 1,
160 | delay: 0,
161 | iterationCount: INFINITE_ANIMATION_ITERATIONS,
162 | type: AnimationType.BOUNCE
163 | }
164 | }
165 | });
166 | };
167 |
168 | private onMapEvent = (
169 | webViewLeafletEvent: WebViewLeafletEvents,
170 | payload?: WebviewLeafletMessagePayload
171 | ) => {
172 | if (!payload && this.state.mapRef?.leafletElement) {
173 | debugger;
174 | const mapCenterPosition: LatLng = {
175 | lat: this.state.mapRef.leafletElement?.getCenter().lat,
176 | lng: this.state.mapRef.leafletElement?.getCenter().lng
177 | };
178 |
179 | payload = {
180 | mapCenterPosition: mapCenterPosition,
181 | bounds: this.state.mapRef.leafletElement?.getBounds(),
182 | zoom: this.state.mapRef.leafletElement?.getZoom()
183 | };
184 | }
185 | this.sendMessage({ event: webViewLeafletEvent, payload });
186 | };
187 |
188 | private setMapRef = (mapRef: any) => {
189 | if (!this.state.mapRef) {
190 | this.setState({ mapRef });
191 | }
192 | };
193 |
194 | render() {
195 | const {
196 | debugMessages,
197 | mapCenterPosition,
198 | mapLayers,
199 | mapMarkers,
200 | mapShapes,
201 | ownPositionMarker,
202 | zoom
203 | } = this.state;
204 | return (
205 |
217 | );
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/HTML/src/mapComponent.view.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useState, useEffect } from "react";
3 | import Measure from "react-measure";
4 | import { Map, LatLng } from "react-leaflet";
5 | import MapLayers from "./MapLayers";
6 | import MapMarkers from "./MapMarkers";
7 | import { SHOW_DEBUG_INFORMATION } from "./MapComponent";
8 | import { WebViewLeafletEvents, MapLayer, MapMarker, MapShape } from "./models";
9 | import MapShapes from "./MapShapes";
10 | import { LatLngExpression } from "leaflet";
11 |
12 | interface MapComponentViewProps {
13 | addDebugMessage: (msg: any) => void;
14 | debugMessages: string[];
15 | mapCenterPosition: LatLng;
16 | mapLayers: MapLayer[];
17 | mapMarkers: MapMarker[];
18 | mapShapes: MapShape[];
19 | onMapEvent: (mapEvent: WebViewLeafletEvents, payload?: any) => void;
20 | ownPositionMarker: MapMarker;
21 | setMapRef: (mapRef: any) => void;
22 | zoom: number;
23 | }
24 |
25 | const MapComponentView: React.FC = ({
26 | addDebugMessage,
27 | debugMessages,
28 | mapCenterPosition,
29 | mapLayers = [],
30 | mapMarkers = [],
31 | mapShapes = [],
32 | onMapEvent,
33 | ownPositionMarker,
34 | setMapRef,
35 | zoom = 13
36 | }: MapComponentViewProps) => {
37 | const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
38 | const [combinedMapMarkers, setCombinedMapMarkers] = useState([]);
39 |
40 | useEffect(() => {
41 | const combinedMapMarkers = mapMarkers;
42 | if (ownPositionMarker) {
43 | combinedMapMarkers.push(ownPositionMarker);
44 | }
45 |
46 | setCombinedMapMarkers(combinedMapMarkers);
47 | }, [mapMarkers, ownPositionMarker]);
48 |
49 | return (
50 | <>
51 | {
54 | const { height, width } = contentRect.bounds;
55 | setDimensions({ height, width });
56 | }}
57 | >
58 | {({ measureRef }) => (
59 |
71 | {dimensions.height > 0 && (
72 |
126 | )}
127 |
128 | )}
129 |
130 | {SHOW_DEBUG_INFORMATION ? (
131 |
145 |
146 | {debugMessages.map((message, index) => {
147 | return - {message}
;
148 | })}
149 |
150 |
151 | ) : null}
152 | >
153 | );
154 | };
155 |
156 | export default MapComponentView;
157 |
--------------------------------------------------------------------------------
/HTML/src/models.ts:
--------------------------------------------------------------------------------
1 | import * as ReactLeaflet from "react-leaflet";
2 | export type LatLng = ReactLeaflet.LatLng;
3 | export type Point = ReactLeaflet.Point;
4 | export type LatLngBounds = ReactLeaflet.LatLngBounds;
5 |
6 | export const OWN_POSTION_MARKER_ID = "OWN_POSTION_MARKER_ID";
7 |
8 | export enum WebViewLeafletEvents {
9 | MAP_COMPONENT_MOUNTED = "MAP_COMPONENT_MOUNTED",
10 | MAP_READY = "MAP_READY",
11 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED",
12 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED",
13 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER",
14 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED",
15 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED",
16 | ON_MOVE_END = "onMoveEnd",
17 | ON_MOVE_START = "onMoveStart",
18 | ON_MOVE = "onMove",
19 | ON_RESIZE = "onResize",
20 | ON_UNLOAD = "onUnload",
21 | ON_VIEW_RESET = "onViewReset",
22 | ON_ZOOM_END = "onZoomEnd",
23 | ON_ZOOM_LEVELS_CHANGE = "onZoomLevelsChange",
24 | ON_ZOOM_START = "onZoomStart",
25 | ON_ZOOM = "onZoom",
26 | ON_MAP_TOUCHED = "onMapClicked",
27 | ON_MAP_MARKER_CLICKED = "onMapMarkerClicked"
28 | // ON_MAP_SHAPE_CLICKED = "onMapShapeClicked" cannot click on shapes yet
29 | }
30 |
31 | export enum AnimationType {
32 | BOUNCE = "bounce",
33 | FADE = "fade",
34 | PULSE = "pulse",
35 | JUMP = "jump",
36 | SPIN = "spin",
37 | WAGGLE = "waggle"
38 | }
39 |
40 | export enum MapLayerType {
41 | IMAGE_LAYER = "ImageOverlay",
42 | TILE_LAYER = "TileLayer",
43 | VECTOR_LAYER = "VectorLayer",
44 | VIDEO_LAYER = "VideoOverlay",
45 | WMS_TILE_LAYER = "WMSTileLayer"
46 | }
47 |
48 | export enum MapShapeType {
49 | CIRCLE = "Circle",
50 | CIRCLE_MARKER = "CircleMarker",
51 | POLYLINE = "Polyline",
52 | POLYGON = "Polygon",
53 | RECTANGLE = "Rectangle"
54 | }
55 |
56 | export const INFINITE_ANIMATION_ITERATIONS: string = "infinite";
57 |
58 | export enum AnimationDirection {
59 | NORMAL = "nomal",
60 | REVERSE = "reverse",
61 | ALTERNATE = "alternate",
62 | ALTERNATE_REVERSE = "alternate-reverse"
63 | }
64 | export interface MapMarkerAnimation {
65 | type: AnimationType;
66 | duration?: number;
67 | delay?: number;
68 | direction?: AnimationDirection;
69 | iterationCount?: number | typeof INFINITE_ANIMATION_ITERATIONS;
70 | }
71 |
72 | export interface MapMarker {
73 | animation?: MapMarkerAnimation;
74 | position: LatLng;
75 | divIcon?: L.DivIcon;
76 | icon: any;
77 | iconAnchor?: Point;
78 | id?: string;
79 | size?: Point;
80 | title?: string;
81 | }
82 |
83 | export interface MapEventMessage {
84 | event?: any;
85 | msg?: string;
86 | error?: string;
87 | payload?: any;
88 | }
89 |
90 | export interface MapLayer {
91 | attribution?: string;
92 | baseLayer?: boolean;
93 | baseLayerIsChecked?: boolean;
94 | baseLayerName?: string;
95 | bounds?: LatLngBounds;
96 | id?: string;
97 | layerType?: MapLayerType;
98 | opacity?: number;
99 | pane?: string;
100 | subLayer?: string;
101 | url?: string;
102 | zIndex?: number;
103 | }
104 |
105 | export interface MapShape {
106 | bounds?: LatLng[];
107 | center?: LatLng;
108 | color?: string;
109 | id?: string;
110 | positions?: LatLng[] | LatLng[][];
111 | radius?: number;
112 | shapeType: MapShapeType;
113 | }
114 |
115 | export interface MapStartupMessage {
116 | mapLayers?: MapLayer[];
117 | mapMarkers?: MapMarker[];
118 | mapShapes?: MapShape[];
119 | mapCenterPosition?: LatLng;
120 | ownPositionMarker?: OwnPositionMarker;
121 | zoom?: number;
122 | }
123 |
124 | export type WebviewLeafletMessagePayload = {
125 | bounds?: LatLngBounds;
126 | mapCenterPosition: LatLng;
127 | mapMarkerID?: string;
128 | touchLatLng?: LatLng;
129 | zoom?: number;
130 | };
131 |
132 | export interface WebviewLeafletMessage {
133 | event?: any;
134 | msg?: string;
135 | error?: string;
136 | payload?: WebviewLeafletMessagePayload;
137 | }
138 |
139 | export type OwnPositionMarker = {
140 | animation: MapMarkerAnimation;
141 | id?: string;
142 | icon: string;
143 | position: LatLng;
144 | size: Point;
145 | };
146 |
--------------------------------------------------------------------------------
/HTML/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/HTML/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready.then(registration => {
142 | registration.unregister();
143 | });
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/HTML/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/HTML/src/styles/markerAnimations.css:
--------------------------------------------------------------------------------
1 | .marker {
2 | background-color: rgba(255, 255, 255, 0);
3 | width: 100%;
4 | height: 100%;
5 | position: relative;
6 | }
7 |
8 | /* div containing the marker parent */
9 | .clearMarkerContainer {
10 | background-color: rgba(57, 57, 216, 0);
11 | display: flex;
12 | justify-content: center;
13 | }
14 |
15 | /* div containing all the animated portions of the marker */
16 | .animationContainer {
17 | display: flex;
18 | justify-content: center;
19 | align-items: flex-end;
20 | }
21 |
22 | @keyframes bounce {
23 | 0% {
24 | transform: scale(1, 0.8) translateY(10px);
25 | }
26 | 45% {
27 | transform: scale(0.8, 1) translateY(-27px);
28 | }
29 | 50% {
30 | transform: scale(0.8, 1) translateY(-30px);
31 | }
32 | 55% {
33 | transform: scale(0.8, 1) translateY(-27px);
34 | }
35 | 100% {
36 | transform: scale(1, 0.8) translateY(10px);
37 | }
38 | }
39 |
40 | @keyframes fade {
41 | 0% {
42 | opacity: 0.1;
43 | }
44 | 50% {
45 | opacity: 1;
46 | }
47 | 100% {
48 | opacity: 0.1;
49 | }
50 | }
51 |
52 | @keyframes pulse {
53 | 0% {
54 | transform: scale(1);
55 | }
56 | 50% {
57 | transform: scale(1.25);
58 | }
59 | 100% {
60 | transform: scale(1);
61 | }
62 | }
63 |
64 | @keyframes jump {
65 | 0% {
66 | transform: none;
67 | }
68 | 50% {
69 | transform: translateY(-2em);
70 | }
71 | }
72 |
73 | @keyframes waggle {
74 | 0% {
75 | transform: none;
76 | }
77 | 50% {
78 | transform: rotateZ(-20deg) scale(1.2);
79 | }
80 | 60% {
81 | transform: rotateZ(25deg) scale(1.2);
82 | }
83 | 67.5% {
84 | transform: rotateZ(-15deg) scale(1.2);
85 | }
86 | 75% {
87 | transform: rotateZ(15deg) scale(1.2);
88 | }
89 | 82.5% {
90 | transform: rotateZ(-12deg) scale(1.2);
91 | }
92 | 85% {
93 | transform: rotateZ(0) scale(1.2);
94 | }
95 | 100% {
96 | transform: rotateZ(0) scale(1);
97 | }
98 | }
99 |
100 | @keyframes spin {
101 | 50% {
102 | transform: rotateZ(-20deg);
103 | animation-timing-function: ease;
104 | }
105 | 100% {
106 | transform: rotateZ(360deg);
107 | }
108 | }
109 |
110 | @keyframes beat {
111 | to {
112 | transform: scale(0.7);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/HTML/src/styles/markers.css:
--------------------------------------------------------------------------------
1 | /*General marker style*/
2 | .marker {
3 | background-color: rgba(255, 255, 255, 0);
4 | width: 100%;
5 | height: 100%;
6 | position: relative;
7 | }
8 |
9 | /* div containing the marker parent */
10 | .clearMarkerContainer {
11 | background-color: rgba(57, 57, 216, 0);
12 | display: flex;
13 | justify-content: center;
14 | }
15 |
16 | /* div containing all the animated portions of the marker */
17 | .animationContainer {
18 | display: flex;
19 | justify-content: center;
20 | align-items: flex-end;
21 | }
22 |
23 | @keyframes bounce {
24 | 0% {
25 | transform: scale(1, 0.8) translateY(10px);
26 | }
27 | 45% {
28 | transform: scale(0.8, 1) translateY(-27px);
29 | }
30 | 50% {
31 | transform: scale(0.8, 1) translateY(-30px);
32 | }
33 | 55% {
34 | transform: scale(0.8, 1) translateY(-27px);
35 | }
36 | 100% {
37 | transform: scale(1, 0.8) translateY(10px);
38 | }
39 | }
40 |
41 | @keyframes fade {
42 | 0% {
43 | opacity: 0.1;
44 | }
45 | 50% {
46 | opacity: 1;
47 | }
48 | 100% {
49 | opacity: 0.1;
50 | }
51 | }
52 |
53 | @keyframes pulse {
54 | 0% {
55 | transform: scale(1);
56 | }
57 | 50% {
58 | transform: scale(1.25);
59 | }
60 | 100% {
61 | transform: scale(1);
62 | }
63 | }
64 |
65 | @keyframes jump {
66 | 0% {
67 | transform: none;
68 | }
69 | 50% {
70 | transform: translateY(-2em);
71 | }
72 | }
73 |
74 | @keyframes waggle {
75 | 0% {
76 | transform: none;
77 | }
78 | 50% {
79 | transform: rotateZ(-20deg) scale(1.2);
80 | }
81 | 60% {
82 | transform: rotateZ(25deg) scale(1.2);
83 | }
84 | 67.5% {
85 | transform: rotateZ(-15deg) scale(1.2);
86 | }
87 | 75% {
88 | transform: rotateZ(15deg) scale(1.2);
89 | }
90 | 82.5% {
91 | transform: rotateZ(-12deg) scale(1.2);
92 | }
93 | 85% {
94 | transform: rotateZ(0) scale(1.2);
95 | }
96 | 100% {
97 | transform: rotateZ(0) scale(1);
98 | }
99 | }
100 |
101 | @keyframes spin {
102 | 50% {
103 | transform: rotateZ(-20deg);
104 | animation-timing-function: ease;
105 | }
106 | 100% {
107 | transform: rotateZ(360deg);
108 | }
109 | }
110 |
111 | @keyframes beat {
112 | to {
113 | transform: scale(0.7);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/HTML/src/testData/mockMapLayers.ts:
--------------------------------------------------------------------------------
1 | import { MapLayer, MapLayerType } from "../models";
2 |
3 | const mockMapLayers: MapLayer[] = [
4 | {
5 | attribution:
6 | '© OpenStreetMap contributors',
7 | baseLayerIsChecked: true,
8 | baseLayerName: "OpenStreetMap.Mapnik",
9 | url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
10 | },
11 | {
12 | attribution:
13 | '© OpenStreetMap contributors',
14 | baseLayerIsChecked: false,
15 | baseLayerName: "OpenStreetMap.BlackAndWhite",
16 | url: "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
17 | },
18 | {
19 | baseLayerName: "WMS Tile Layer",
20 | subLayer: "nasa:bluemarble",
21 | layerType: MapLayerType.WMS_TILE_LAYER,
22 | url: "https://demo.boundlessgeo.com/geoserver/ows"
23 | },
24 | {
25 | baseLayerName: "Image",
26 | layerType: MapLayerType.IMAGE_LAYER,
27 | url: "http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg",
28 | bounds: [
29 | [40.712216, -74.22655],
30 | [40.773941, -74.12544]
31 | ]
32 | }
33 | ];
34 |
35 | export default mockMapLayers;
36 |
--------------------------------------------------------------------------------
/HTML/src/testData/mockMapMarkers.ts:
--------------------------------------------------------------------------------
1 | import * as svgIcons from "./svgIcons";
2 | import { LatLng } from "leaflet";
3 | import { MapMarker, AnimationType } from "../models";
4 |
5 | const emoji = ["😴", "😄", "😃", "⛔", "🎠", "🚓", "🚇"];
6 | const duration = Math.floor(Math.random() * 3) + 1;
7 | const delay = Math.floor(Math.random()) * 0.5;
8 | const iterationCount = "infinite";
9 |
10 | const mapMarkers: MapMarker[] = [
11 | {
12 | id: "2",
13 | position: { lat: 37.06452161, lng: -75.67364786 },
14 | icon: "😴",
15 | size: [64, 64],
16 | animation: {
17 | duration,
18 | delay,
19 | iterationCount,
20 | type: AnimationType.PULSE
21 | }
22 | },
23 | {
24 | id: "19",
25 | position: { lat: 36.46410354, lng: -75.6432701 },
26 | icon:
27 | "https://www.catster.com/wp-content/uploads/2018/07/Savannah-cat-long-body-shot.jpg",
28 | size: [32, 32],
29 | animation: {
30 | duration,
31 | delay,
32 | iterationCount,
33 | type: AnimationType.BOUNCE
34 | }
35 | },
36 | {
37 | id: "100",
38 | position: new LatLng(37.23310632, -76.23518332),
39 | icon: emoji[Math.floor(Math.random() * emoji.length)],
40 | animation: {
41 | duration,
42 | delay,
43 | iterationCount,
44 | type: AnimationType.WAGGLE
45 | }
46 | },
47 | {
48 | id: "1",
49 | position: { lat: 36.46410354, lng: -75.6432701 },
50 | icon: "😴",
51 | size: [32, 32],
52 | animation: {
53 | type: AnimationType.SPIN,
54 | duration,
55 | delay,
56 | iterationCount
57 | }
58 | },
59 | {
60 | id: "1000",
61 | position: new LatLng(36.60061515, -76.48888338),
62 | icon: svgIcons.greenCircle,
63 | animation: {
64 | duration,
65 | delay,
66 | iterationCount,
67 | type: AnimationType.PULSE
68 | }
69 | },
70 | {
71 | id: Math.floor(Math.random() * 1000).toString(),
72 | position: { lat: 37.0580835, lng: -75.82318747 },
73 | icon: "Fish",
74 | animation: {
75 | type: AnimationType.WAGGLE,
76 | duration,
77 | delay,
78 | iterationCount
79 | }
80 | },
81 | {
82 | id: Math.floor(Math.random() * 1000).toString(),
83 | position: { lat: 37.23310632, lng: -76.23518332 },
84 | icon: emoji[Math.floor(Math.random() * emoji.length)],
85 | size: [4, 4],
86 | animation: {
87 | type: AnimationType.PULSE,
88 | duration,
89 | delay,
90 | iterationCount
91 | }
92 | }
93 | /*
94 | {
95 | id: Math.floor(Math.random() * 1000),
96 | coords: [36.94994253, -76.64318409],
97 | icon: emoji[Math.floor(Math.random() * emoji.length)],
98 | animation: {
99 | name: animations[Math.floor(Math.random() * animations.length)],
100 | duration: Math.floor(Math.random() * 3) + 1,
101 | delay: Math.floor(Math.random()) * 0.5,
102 | iterationCount
103 | }
104 | },
105 | {
106 | id: Math.floor(Math.random() * 1000),
107 | coords: [37.19810239, -76.28058546],
108 | icon: emoji[Math.floor(Math.random() * emoji.length)],
109 | animation: {
110 | name: animations[Math.floor(Math.random() * animations.length)],
111 | duration: Math.floor(Math.random() * 3) + 1,
112 | delay: Math.floor(Math.random()) * 0.5,
113 | iterationCount
114 | }
115 | },
116 | {
117 | id: Math.floor(Math.random() * 1000),
118 | coords: [37.02416165, -76.56052521],
119 | icon: emoji[Math.floor(Math.random() * emoji.length)],
120 | animation: {
121 | name: animations[Math.floor(Math.random() * animations.length)],
122 | duration: Math.floor(Math.random() * 3) + 1,
123 | delay: Math.floor(Math.random()) * 0.5,
124 | iterationCount
125 | }
126 | },
127 | {
128 | id: Math.floor(Math.random() * 1000),
129 | coords: [36.91541467, -75.49279245],
130 | icon: emoji[Math.floor(Math.random() * emoji.length)],
131 | animation: {
132 | name: animations[Math.floor(Math.random() * animations.length)],
133 | duration: Math.floor(Math.random() * 3) + 1,
134 | delay: Math.floor(Math.random()) * 0.5,
135 | iterationCount
136 | }
137 | },
138 | {
139 | id: Math.floor(Math.random() * 1000),
140 | coords: [36.70503123, -76.32755185],
141 | icon: emoji[Math.floor(Math.random() * emoji.length)],
142 | animation: {
143 | name: animations[Math.floor(Math.random() * animations.length)],
144 | duration: Math.floor(Math.random() * 3) + 1,
145 | delay: Math.floor(Math.random()) * 0.5,
146 | iterationCount
147 | }
148 | },
149 | {
150 | id: Math.floor(Math.random() * 1000),
151 | coords: [36.31605891, -76.45141618],
152 | icon: emoji[Math.floor(Math.random() * emoji.length)],
153 | animation: {
154 | name: animations[Math.floor(Math.random() * animations.length)],
155 | duration: Math.floor(Math.random() * 3) + 1,
156 | delay: Math.floor(Math.random()) * 0.5,
157 | iterationCount
158 | }
159 | },
160 | {
161 | id: Math.floor(Math.random() * 1000),
162 | coords: [36.59436803, -76.89486842],
163 | icon: emoji[Math.floor(Math.random() * emoji.length)],
164 | animation: {
165 | name: animations[Math.floor(Math.random() * animations.length)],
166 | duration: Math.floor(Math.random() * 3) + 1,
167 | delay: Math.floor(Math.random()) * 0.5,
168 | iterationCount
169 | }
170 | },
171 | {
172 | id: Math.floor(Math.random() * 1000),
173 | coords: [37.35740877, -75.77910112],
174 | icon: emoji[Math.floor(Math.random() * emoji.length)],
175 | animation: {
176 | name: animations[Math.floor(Math.random() * animations.length)],
177 | duration: Math.floor(Math.random() * 3) + 1,
178 | delay: Math.floor(Math.random()) * 0.5,
179 | iterationCount
180 | }
181 | },
182 | {
183 | id: Math.floor(Math.random() * 1000),
184 | coords: [37.31509182, -76.76693784],
185 | icon: emoji[Math.floor(Math.random() * emoji.length)],
186 | animation: {
187 | name: animations[Math.floor(Math.random() * animations.length)],
188 | duration: Math.floor(Math.random() * 3) + 1,
189 | delay: Math.floor(Math.random()) * 0.5,
190 | iterationCount
191 | }
192 | },
193 | {
194 | id: Math.floor(Math.random() * 1000),
195 | coords: [36.91815909, -76.06707072],
196 | icon: emoji[Math.floor(Math.random() * emoji.length)],
197 | animation: {
198 | name: animations[Math.floor(Math.random() * animations.length)],
199 | duration: Math.floor(Math.random() * 3) + 1,
200 | delay: Math.floor(Math.random()) * 0.5,
201 | iterationCount
202 | }
203 | },
204 | {
205 | id: Math.floor(Math.random() * 1000),
206 | coords: [36.611917, -75.76758822],
207 | icon: emoji[Math.floor(Math.random() * emoji.length)],
208 | animation: {
209 | name: animations[Math.floor(Math.random() * animations.length)],
210 | duration: Math.floor(Math.random() * 3) + 1,
211 | delay: Math.floor(Math.random()) * 0.5,
212 | iterationCount
213 | }
214 | },
215 | {
216 | id: Math.floor(Math.random() * 1000),
217 | coords: [36.79520769, -76.3959497],
218 | icon: emoji[Math.floor(Math.random() * emoji.length)],
219 | animation: {
220 | name: animations[Math.floor(Math.random() * animations.length)],
221 | duration: Math.floor(Math.random() * 3) + 1,
222 | delay: Math.floor(Math.random()) * 0.5,
223 | iterationCount
224 | }
225 | },
226 | {
227 | id: Math.floor(Math.random() * 1000),
228 | coords: [37.42854666, -75.95883052],
229 | icon: emoji[Math.floor(Math.random() * emoji.length)],
230 | animation: {
231 | name: animations[Math.floor(Math.random() * animations.length)],
232 | duration: Math.floor(Math.random() * 3) + 1,
233 | delay: Math.floor(Math.random()) * 0.5,
234 | iterationCount
235 | }
236 | },
237 | {
238 | id: Math.floor(Math.random() * 1000),
239 | coords: [36.78673099, -76.90459724],
240 | icon: emoji[Math.floor(Math.random() * emoji.length)],
241 | animation: {
242 | name: animations[Math.floor(Math.random() * animations.length)],
243 | duration: Math.floor(Math.random() * 3) + 1,
244 | delay: Math.floor(Math.random()) * 0.5,
245 | iterationCount
246 | }
247 | },
248 | {
249 | id: Math.floor(Math.random() * 1000),
250 | coords: [37.20966767, -75.58799685],
251 | icon: emoji[Math.floor(Math.random() * emoji.length)],
252 | animation: {
253 | name: animations[Math.floor(Math.random() * animations.length)],
254 | duration: Math.floor(Math.random() * 3) + 1,
255 | delay: Math.floor(Math.random()) * 0.5,
256 | iterationCount
257 | }
258 | } */
259 | ];
260 | export default mapMarkers;
261 |
--------------------------------------------------------------------------------
/HTML/src/testData/mockMapShapes.ts:
--------------------------------------------------------------------------------
1 | import { MapShapeType, MapShape } from "../models";
2 |
3 | export const circle: MapShape = {
4 | shapeType: MapShapeType.CIRCLE,
5 | color: "#123123",
6 | id: "1",
7 | center: { lat: 34.225727, lng: -77.94471 },
8 | radius: 2000
9 | };
10 |
11 | export const circleMarker: MapShape = {
12 | shapeType: MapShapeType.CIRCLE_MARKER,
13 | color: "red",
14 | id: "2",
15 | center: { lat: 38.437424, lng: -78.867912 },
16 | radius: 15
17 | };
18 |
19 | export const polygon: MapShape = {
20 | shapeType: MapShapeType.POLYGON,
21 | color: "blue",
22 | id: "3",
23 | positions: [
24 | { lat: 38.80118939192329, lng: -74.69604492187501 },
25 | { lat: 38.19502155795575, lng: -74.65209960937501 },
26 | { lat: 39.07890809706475, lng: -71.46606445312501 }
27 | ]
28 | };
29 |
30 | export const multiPolygon: MapShape = {
31 | shapeType: MapShapeType.POLYGON,
32 | color: "violet",
33 | id: "4",
34 | positions: [
35 | [
36 | { lat: 37.13842453422676, lng: -74.28955078125001 },
37 | { lat: 36.4433803110554, lng: -74.26208496093751 },
38 | { lat: 36.43896124085948, lng: -73.00964355468751 },
39 | { lat: 36.43896124085948, lng: -73.00964355468751 }
40 | ],
41 | [
42 | { lat: 37.505368263398104, lng: -72.38891601562501 },
43 | { lat: 37.309014074275915, lng: -71.96594238281251 },
44 | { lat: 36.69044623523481, lng: -71.87805175781251 },
45 | { lat: 36.58024660149866, lng: -72.75146484375001 },
46 | { lat: 37.36579146999664, lng: -72.88330078125001 }
47 | ]
48 | ]
49 | };
50 |
51 | export const polyline: MapShape = {
52 | shapeType: MapShapeType.POLYLINE,
53 | color: "orange",
54 | id: "5",
55 | positions: [
56 | { lat: 35.411438052435486, lng: -78.67858886718751 },
57 | { lat: 35.9602229692967, lng: -79.18945312500001 },
58 | { lat: 35.97356075349624, lng: -78.30505371093751 }
59 | ]
60 | };
61 |
62 | export const multiPolyline: MapShape = {
63 | shapeType: MapShapeType.POLYLINE,
64 | color: "purple",
65 | id: "5a",
66 | positions: [
67 | [
68 | { lat: 36.36822190085111, lng: -79.26086425781251 },
69 | { lat: 36.659606226479696, lng: -79.28833007812501 },
70 | { lat: 36.721273880045004, lng: -79.81018066406251 }
71 | ],
72 | [
73 | { lat: 35.43381992014202, lng: -79.79370117187501 },
74 | { lat: 35.44277092585766, lng: -81.23840332031251 },
75 | { lat: 35.007502842952896, lng: -80.837402343750017 }
76 | ]
77 | ]
78 | };
79 |
80 | export const rectangle: MapShape = {
81 | shapeType: MapShapeType.RECTANGLE,
82 | color: "yellow",
83 | id: "6",
84 | bounds: [
85 | { lat: 36.5, lng: -75.7 },
86 | { lat: 38.01, lng: -73.13 }
87 | ]
88 | };
89 |
90 | export default [circle, circleMarker, polygon, polyline, rectangle];
91 |
--------------------------------------------------------------------------------
/HTML/src/testData/svgIcons.ts:
--------------------------------------------------------------------------------
1 | export const greenCircle = ``;
4 |
--------------------------------------------------------------------------------
/HTML/src/utilities.test.ts.old:
--------------------------------------------------------------------------------
1 | import {
2 | convertWebViewLeafletLatLngToNumberArray,
3 | convertWebViewLeafletLatLngBoundsToLeaftletBounds,
4 | getAnimatedHTMLString
5 | } from "./utilities";
6 | import {
7 | WebViewLeafletLatLng,
8 | WebViewLeafletLatLngBounds
9 | } from "../../WebViewLeaflet/models";
10 | import { AnimationType } from "./models";
11 |
12 | const singleLatLng: WebViewLeafletLatLng = { lat: 34.225727, lng: -77.94471 };
13 | const latLngArray: WebViewLeafletLatLng[] = [
14 | { lat: 38.80118939192329, lng: -74.69604492187501 },
15 | { lat: 38.19502155795575, lng: -74.65209960937501 },
16 | { lat: 39.07890809706475, lng: -71.46606445312501 }
17 | ];
18 | const latLng2DArray: WebViewLeafletLatLng[][] = [
19 | [
20 | { lat: 37.13842453422676, lng: -74.28955078125001 },
21 | { lat: 36.4433803110554, lng: -74.26208496093751 },
22 | { lat: 36.43896124085948, lng: -73.00964355468751 },
23 | { lat: 36.43896124085948, lng: -73.00964355468751 }
24 | ],
25 | [
26 | { lat: 37.505368263398104, lng: -72.38891601562501 },
27 | { lat: 37.309014074275915, lng: -71.96594238281251 },
28 | { lat: 36.69044623523481, lng: -71.87805175781251 },
29 | { lat: 36.58024660149866, lng: -72.75146484375001 },
30 | { lat: 37.36579146999664, lng: -72.88330078125001 }
31 | ]
32 | ];
33 |
34 | /* describe('convertWebViewLeafletLatLngToNumberArray', () => {
35 | it('can covert a single lat lng to a number array ', () => {
36 | const singleConverted = convertWebViewLeafletLatLngToNumberArray(
37 | singleLatLng
38 | );
39 | expect(singleConverted).toEqual([34.225727, -77.94471]);
40 | });
41 |
42 | it('can covert a an array of latLng to an array of number arrays', () => {
43 | const convertedLatLongArray = convertWebViewLeafletLatLngToNumberArray(
44 | latLngArray
45 | );
46 | expect(convertedLatLongArray).toEqual([
47 | [38.80118939192329, -74.69604492187501],
48 | [38.19502155795575, -74.65209960937501],
49 | [39.07890809706475, -71.46606445312501]
50 | ]);
51 | });
52 |
53 | it('can covert a 2D Array of latLngs to a 2D number array ', () => {
54 | const latLng2DArrayConverted = convertWebViewLeafletLatLngToNumberArray(
55 | latLng2DArray
56 | );
57 | expect(latLng2DArrayConverted).toEqual([
58 | [
59 | [37.13842453422676, -74.28955078125001],
60 | [36.4433803110554, -74.26208496093751],
61 | [36.43896124085948, -73.00964355468751],
62 | [36.43896124085948, -73.00964355468751]
63 | ],
64 | [
65 | [37.505368263398104, -72.38891601562501],
66 | [37.309014074275915, -71.96594238281251],
67 | [36.69044623523481, -71.87805175781251],
68 | [36.58024660149866, -72.75146484375001],
69 | [37.36579146999664, -72.88330078125001]
70 | ]
71 | ]);
72 | });
73 | }); */
74 |
75 | const cornerBounds: WebViewLeafletLatLngBounds = {
76 | southWest: { lat: 36.665099, lng: -76.842042 },
77 | northEast: { lat: 37.365855, lng: -76.158245 }
78 | };
79 | const arrayBounds: WebViewLeafletLatLngBounds = [
80 | { lat: 38.89688, lng: -77.302505 },
81 | { lat: 37.829395, lng: -76.756299 }
82 | ];
83 |
84 | /* describe('convertWebViewLeafletLatLngBoundsToLeaftletBounds', () => {
85 | it('can covert WebViewLeafletLatLngBoundsCorner objects', () => {
86 | const convertedBounds = convertWebViewLeafletLatLngBoundsToLeaftletBounds(
87 | cornerBounds
88 | );
89 |
90 | expect(convertedBounds).toEqual({
91 | southWest: [36.665099, -76.842042],
92 | northEast: [37.365855, -76.158245]
93 | });
94 | });
95 | it('can covert WebViewLeafletLatLngBounds[] objects', () => {
96 | const convertedBounds = convertWebViewLeafletLatLngBoundsToLeaftletBounds(
97 | arrayBounds
98 | );
99 |
100 | expect(convertedBounds).toEqual([
101 | [38.89688, -77.302505],
102 | [37.829395, -76.756299]
103 | ]);
104 | });
105 | }); */
106 |
107 | const mapMarker = {
108 | id: 2,
109 | coords: { lat: 37.06452161, lng: -75.67364786 },
110 | icon: "😴",
111 | size: [64, 64],
112 | animation: {
113 | duration: 1,
114 | delay: 0.5,
115 | type: AnimationType.BOUNCE
116 | }
117 | };
118 |
119 | /* describe('getAnimatedHTMLString', () => {
120 | it('returns a div with an animated emoji', () => {
121 | const div = getAnimatedHTMLString(mapMarker.icon, mapMarker.animation);
122 | expect(div).toBe(``);
130 | });
131 | }); */
132 |
--------------------------------------------------------------------------------
/HTML/src/utilities.ts:
--------------------------------------------------------------------------------
1 | import L, { DivIcon } from "leaflet";
2 | import base64Image from "./webBase64Image";
3 | import { MapMarker, MapMarkerAnimation } from "./models";
4 | import { Point } from "react-leaflet";
5 |
6 | export const createDivIcon = (mapMarker: MapMarker): DivIcon => {
7 | let divIcon: DivIcon = L.divIcon({
8 | className: "clearMarkerContainer",
9 | html: mapMarker.animation
10 | ? getAnimatedHTMLString(
11 | mapMarker.icon || "📍",
12 | mapMarker.animation || null,
13 | mapMarker.size || [24, 24]
14 | )
15 | : getUnanimatedHTMLString(mapMarker.icon, mapMarker.size),
16 | iconAnchor: mapMarker.iconAnchor || null
17 | });
18 | return divIcon;
19 | };
20 |
21 | /*
22 | Get the HTML string containing the icon div, and animation parameters
23 | */
24 | export const getAnimatedHTMLString = (
25 | icon: any,
26 | animation: MapMarkerAnimation,
27 | size: Point = [24, 24]
28 | ) => {
29 | return `
37 | ${getIconFromEmojiOrImageOrSVG(icon, size)}
38 |
`;
39 | };
40 |
41 | const getUnanimatedHTMLString = (icon: any, size: Point = [24, 24]): string => {
42 | return `${getIconFromEmojiOrImageOrSVG(
43 | icon,
44 | size
45 | )}
`;
46 | };
47 |
48 | const getIconFromEmojiOrImageOrSVG = (icon: any, size: Point) => {
49 | if (icon.includes("svg") || icon.includes("SVG")) {
50 | //@ts-ignore
51 | return `
52 | ${icon}
53 |
`;
54 | } else if (icon.includes("//") && icon.includes("http")) {
55 | //@ts-ignore
56 |
57 | return `
`;
58 | } else if (icon.includes("base64")) {
59 | //@ts-ignore
60 |
61 | return `
`;
62 | } else {
63 | return `${icon}
`;
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/HTML/src/webBase64Image.ts:
--------------------------------------------------------------------------------
1 | const base64Image =
2 | 'data:image/gif;base64,R0lGODlhPQBEAPeoAJosM//AwO/AwHVYZ/z595kzAP/s7P+goOXMv8+fhw/v739/f+8PD98fH/8mJl+fn/9ZWb8/PzWlwv///6wWGbImAPgTEMImIN9gUFCEm/gDALULDN8PAD6atYdCTX9gUNKlj8wZAKUsAOzZz+UMAOsJAP/Z2ccMDA8PD/95eX5NWvsJCOVNQPtfX/8zM8+QePLl38MGBr8JCP+zs9myn/8GBqwpAP/GxgwJCPny78lzYLgjAJ8vAP9fX/+MjMUcAN8zM/9wcM8ZGcATEL+QePdZWf/29uc/P9cmJu9MTDImIN+/r7+/vz8/P8VNQGNugV8AAF9fX8swMNgTAFlDOICAgPNSUnNWSMQ5MBAQEJE3QPIGAM9AQMqGcG9vb6MhJsEdGM8vLx8fH98AANIWAMuQeL8fABkTEPPQ0OM5OSYdGFl5jo+Pj/+pqcsTE78wMFNGQLYmID4dGPvd3UBAQJmTkP+8vH9QUK+vr8ZWSHpzcJMmILdwcLOGcHRQUHxwcK9PT9DQ0O/v70w5MLypoG8wKOuwsP/g4P/Q0IcwKEswKMl8aJ9fX2xjdOtGRs/Pz+Dg4GImIP8gIH0sKEAwKKmTiKZ8aB/f39Wsl+LFt8dgUE9PT5x5aHBwcP+AgP+WltdgYMyZfyywz78AAAAAAAD///8AAP9mZv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKgALAAAAAA9AEQAAAj/AFEJHEiwoMGDCBMqXMiwocAbBww4nEhxoYkUpzJGrMixogkfGUNqlNixJEIDB0SqHGmyJSojM1bKZOmyop0gM3Oe2liTISKMOoPy7GnwY9CjIYcSRYm0aVKSLmE6nfq05QycVLPuhDrxBlCtYJUqNAq2bNWEBj6ZXRuyxZyDRtqwnXvkhACDV+euTeJm1Ki7A73qNWtFiF+/gA95Gly2CJLDhwEHMOUAAuOpLYDEgBxZ4GRTlC1fDnpkM+fOqD6DDj1aZpITp0dtGCDhr+fVuCu3zlg49ijaokTZTo27uG7Gjn2P+hI8+PDPERoUB318bWbfAJ5sUNFcuGRTYUqV/3ogfXp1rWlMc6awJjiAAd2fm4ogXjz56aypOoIde4OE5u/F9x199dlXnnGiHZWEYbGpsAEA3QXYnHwEFliKAgswgJ8LPeiUXGwedCAKABACCN+EA1pYIIYaFlcDhytd51sGAJbo3onOpajiihlO92KHGaUXGwWjUBChjSPiWJuOO/LYIm4v1tXfE6J4gCSJEZ7YgRYUNrkji9P55sF/ogxw5ZkSqIDaZBV6aSGYq/lGZplndkckZ98xoICbTcIJGQAZcNmdmUc210hs35nCyJ58fgmIKX5RQGOZowxaZwYA+JaoKQwswGijBV4C6SiTUmpphMspJx9unX4KaimjDv9aaXOEBteBqmuuxgEHoLX6Kqx+yXqqBANsgCtit4FWQAEkrNbpq7HSOmtwag5w57GrmlJBASEU18ADjUYb3ADTinIttsgSB1oJFfA63bduimuqKB1keqwUhoCSK374wbujvOSu4QG6UvxBRydcpKsav++Ca6G8A6Pr1x2kVMyHwsVxUALDq/krnrhPSOzXG1lUTIoffqGR7Goi2MAxbv6O2kEG56I7CSlRsEFKFVyovDJoIRTg7sugNRDGqCJzJgcKE0ywc0ELm6KBCCJo8DIPFeCWNGcyqNFE06ToAfV0HBRgxsvLThHn1oddQMrXj5DyAQgjEHSAJMWZwS3HPxT/QMbabI/iBCliMLEJKX2EEkomBAUCxRi42VDADxyTYDVogV+wSChqmKxEKCDAYFDFj4OmwbY7bDGdBhtrnTQYOigeChUmc1K3QTnAUfEgGFgAWt88hKA6aCRIXhxnQ1yg3BCayK44EWdkUQcBByEQChFXfCB776aQsG0BIlQgQgE8qO26X1h8cEUep8ngRBnOy74E9QgRgEAC8SvOfQkh7FDBDmS43PmGoIiKUUEGkMEC/PJHgxw0xH74yx/3XnaYRJgMB8obxQW6kL9QYEJ0FIFgByfIL7/IQAlvQwEpnAC7DtLNJCKUoO/w45c44GwCXiAFB/OXAATQryUxdN4LfFiwgjCNYg+kYMIEFkCKDs6PKAIJouyGWMS1FSKJOMRB/BoIxYJIUXFUxNwoIkEKPAgCBZSQHQ1A2EWDfDEUVLyADj5AChSIQW6gu10bE/JG2VnCZGfo4R4d0sdQoBAHhPjhIB94v/wRoRKQWGRHgrhGSQJxCS+0pCZbEhAAOw==';
3 |
4 | export default base64Image;
5 |
--------------------------------------------------------------------------------
/HTML/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "noEmit": true,
18 | "jsx": "react",
19 | "strict": true,
20 | "strictNullChecks": false,
21 | "isolatedModules": true
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/WebViewLeaflet/ActivityOverlay.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, ActivityIndicator, StyleSheet } from 'react-native';
3 |
4 | interface Props {}
5 |
6 | export const ActivityOverlay: React.FC = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | const styles = StyleSheet.create({
17 | activityOverlayStyle: {
18 | ...StyleSheet.absoluteFillObject,
19 | backgroundColor: 'rgba(255, 255, 255, .5)',
20 | display: 'flex',
21 | justifyContent: 'center',
22 | alignContent: 'center',
23 | borderRadius: 5
24 | },
25 | activityIndicatorContainer: {
26 | backgroundColor: 'lightgray',
27 | padding: 10,
28 | borderRadius: 50,
29 | alignSelf: 'center',
30 | shadowColor: '#000000',
31 | shadowOffset: {
32 | width: 0,
33 | height: 3
34 | },
35 | shadowRadius: 5,
36 | shadowOpacity: 1.0
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/WebViewLeaflet/DebugMessageBox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ScrollView, Text, View } from "react-native";
3 |
4 | export interface Props {
5 | debugMessages: string[];
6 | doShowDebugMessages: boolean;
7 | }
8 |
9 | const DebugMessageBox = ({
10 | debugMessages = [],
11 | doShowDebugMessages = false
12 | }: Props) => {
13 | if (doShowDebugMessages) {
14 | return (
15 |
27 |
28 | {debugMessages.map((msg, idx) => {
29 | if (typeof msg === "object") {
30 | return (
31 | {`- ${JSON.stringify(
32 | msg
33 | )}`}
34 | );
35 | }
36 | return {`- ${msg}`};
37 | })}
38 |
39 |
40 | );
41 | }
42 | return null;
43 | };
44 |
45 | export default DebugMessageBox;
46 |
--------------------------------------------------------------------------------
/WebViewLeaflet/WebViewLeaflet.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { WebView } from "react-native-webview";
3 | import AssetUtils from "expo-asset-utils";
4 | import { Asset } from "expo-asset";
5 | import WebViewLeafletView from "./WebViewLeaflet.view";
6 | import {
7 | MapMarker,
8 | WebviewLeafletMessage,
9 | MapStartupMessage,
10 | WebViewLeafletEvents,
11 | MapLayer,
12 | MapShape,
13 | OwnPositionMarker,
14 | OWN_POSTION_MARKER_ID
15 | } from "./models";
16 | import { ActivityOverlay } from "./ActivityOverlay";
17 | import * as FileSystem from "expo-file-system";
18 | import { LatLng } from "react-leaflet";
19 | import isEqual from "lodash.isequal";
20 | // @ts-ignore node types
21 | const INDEX_FILE_PATH = require(`./assets/index.html`);
22 |
23 | export interface WebViewLeafletProps {
24 | backgroundColor?: string;
25 | doShowDebugMessages?: boolean;
26 | loadingIndicator?: () => React.ReactElement;
27 | onError?: (syntheticEvent: any) => void;
28 | onLoadEnd?: () => void;
29 | onLoadStart?: () => void;
30 | onMessageReceived: (message: WebviewLeafletMessage) => void;
31 | mapLayers?: MapLayer[];
32 | mapMarkers?: MapMarker[];
33 | mapShapes?: MapShape[];
34 | mapCenterPosition?: LatLng;
35 | ownPositionMarker?: OwnPositionMarker;
36 | zoom?: number;
37 | }
38 |
39 | interface State {
40 | debugMessages: string[];
41 | mapCurrentCenterPosition: LatLng;
42 | webviewContent: string;
43 | isLoading: boolean;
44 | }
45 |
46 | class WebViewLeaflet extends React.Component {
47 | private webViewRef: any;
48 | static defaultProps = {
49 | backgroundColor: "#FAEBD7",
50 | doDisplayCenteringButton: true,
51 | doShowDebugMessages: false,
52 | loadingIndicator: () => {
53 | return ;
54 | },
55 | onError: (syntheticEvent: any) => {},
56 | onLoadEnd: () => {},
57 | onLoadStart: () => {}
58 | };
59 |
60 | constructor(props) {
61 | super(props);
62 | this.state = {
63 | debugMessages: [],
64 | isLoading: null,
65 | mapCurrentCenterPosition: null,
66 | webviewContent: null
67 | };
68 | this.webViewRef = null;
69 | }
70 |
71 | componentDidMount = () => {
72 | this.loadHTMLFile();
73 | };
74 |
75 | private loadHTMLFile = async () => {
76 | try {
77 | let asset: Asset = await AssetUtils.resolveAsync(INDEX_FILE_PATH);
78 | let fileString: string = await FileSystem.readAsStringAsync(
79 | asset.localUri
80 | );
81 |
82 | this.setState({ webviewContent: fileString });
83 | } catch (error) {
84 | console.warn(error);
85 | console.warn("Unable to resolve index file");
86 | }
87 | };
88 |
89 | componentDidUpdate = (prevProps: WebViewLeafletProps, prevState: State) => {
90 | const { webviewContent } = this.state;
91 | const {
92 | mapCenterPosition,
93 | mapMarkers,
94 | mapLayers,
95 | mapShapes,
96 | ownPositionMarker,
97 | zoom
98 | } = this.props;
99 |
100 | if (!prevState.webviewContent && webviewContent) {
101 | this.updateDebugMessages("file loaded");
102 | }
103 | if (!isEqual(mapCenterPosition, prevProps.mapCenterPosition)) {
104 | this.sendMessage({ mapCenterPosition });
105 | }
106 | if (!isEqual(mapMarkers, prevProps.mapMarkers)) {
107 | this.sendMessage({ mapMarkers });
108 | }
109 | if (!isEqual(mapLayers, prevProps.mapLayers)) {
110 | this.sendMessage({ mapLayers });
111 | }
112 | if (!isEqual(mapShapes, prevProps.mapShapes)) {
113 | this.sendMessage({ mapShapes });
114 | }
115 | if (!isEqual(ownPositionMarker, prevProps.ownPositionMarker)) {
116 | this.sendMessage({ ownPositionMarker });
117 | }
118 | if (zoom !== prevProps.zoom) {
119 | this.sendMessage({ zoom });
120 | }
121 | };
122 |
123 | private setMapCenterPosition = () => {
124 | const { mapCurrentCenterPosition } = this.state;
125 | const { mapCenterPosition } = this.props;
126 |
127 | if (!isEqual(mapCenterPosition, mapCurrentCenterPosition)) {
128 | this.setState({
129 | mapCurrentCenterPosition: mapCenterPosition
130 | });
131 | this.sendMessage({
132 | mapCenterPosition
133 | });
134 | }
135 | };
136 |
137 | // Handle messages received from webview contents
138 | private handleMessage = (data: string) => {
139 | const { onMessageReceived } = this.props;
140 |
141 | let message: WebviewLeafletMessage = JSON.parse(data);
142 | this.updateDebugMessages(`received: ${JSON.stringify(message)}`);
143 | if (message.msg === WebViewLeafletEvents.MAP_READY) {
144 | this.sendStartupMessage();
145 | }
146 | if (message.event === WebViewLeafletEvents.ON_MOVE_END) {
147 | this.setState({
148 | mapCurrentCenterPosition: message.payload.mapCenterPosition
149 | });
150 | }
151 | onMessageReceived(message);
152 | };
153 |
154 | // Send message to webview
155 | private sendMessage = (payload: object) => {
156 | this.updateDebugMessages(`sending: ${payload}`);
157 |
158 | this.webViewRef?.injectJavaScript(
159 | `window.postMessage(${JSON.stringify(payload)}, '*');`
160 | );
161 | };
162 |
163 | // Send a startup message with initalizing values to the map
164 | private sendStartupMessage = () => {
165 | let startupMessage: MapStartupMessage = {};
166 | const {
167 | mapLayers,
168 | mapMarkers,
169 | mapShapes,
170 | mapCenterPosition,
171 | ownPositionMarker,
172 | zoom = 7
173 | } = this.props;
174 | if (mapLayers) {
175 | startupMessage.mapLayers = mapLayers;
176 | }
177 | if (mapMarkers) {
178 | startupMessage.mapMarkers = mapMarkers;
179 | }
180 | if (mapCenterPosition) {
181 | startupMessage.mapCenterPosition = mapCenterPosition;
182 | }
183 | if (mapShapes) {
184 | startupMessage.mapShapes = mapShapes;
185 | }
186 | if (ownPositionMarker) {
187 | startupMessage.ownPositionMarker = {
188 | ...ownPositionMarker,
189 | id: OWN_POSTION_MARKER_ID
190 | };
191 | }
192 |
193 | startupMessage.zoom = zoom;
194 |
195 | this.setState({ isLoading: false });
196 | this.updateDebugMessages("sending startup message");
197 |
198 | this.webViewRef.injectJavaScript(
199 | `window.postMessage(${JSON.stringify(startupMessage)}, '*');`
200 | );
201 | };
202 |
203 | // Add a new debug message to the debug message array
204 | private updateDebugMessages = (debugMessage: string) => {
205 | this.setState({
206 | debugMessages: [...this.state.debugMessages, debugMessage]
207 | });
208 | };
209 |
210 | private onError = (syntheticEvent: any) => {
211 | this.props.onError(syntheticEvent);
212 | };
213 | private onLoadEnd = () => {
214 | this.setState({ isLoading: false });
215 | this.props.onLoadEnd();
216 | };
217 | private onLoadStart = () => {
218 | this.setState({ isLoading: true });
219 | this.props.onLoadStart();
220 | };
221 |
222 | // Output rendered item to screen
223 | render() {
224 | const { debugMessages, webviewContent } = this.state;
225 | const {
226 | backgroundColor,
227 | doShowDebugMessages,
228 | loadingIndicator
229 | } = this.props;
230 |
231 | if (webviewContent) {
232 | return (
233 | {
244 | this.webViewRef = ref;
245 | }}
246 | />
247 | );
248 | } else {
249 | return null;
250 | }
251 | }
252 | }
253 |
254 | export default WebViewLeaflet;
255 |
--------------------------------------------------------------------------------
/WebViewLeaflet/WebViewLeaflet.view.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 | import { StyleSheet, View, NativeSyntheticEvent } from 'react-native';
3 | import { WebView } from 'react-native-webview';
4 | import { Asset } from 'expo-asset';
5 | import DebugMessageBox from './DebugMessageBox';
6 | import { WebViewError } from 'react-native-webview/lib/WebViewTypes';
7 |
8 | export interface Props {
9 | backgroundColor: string;
10 | debugMessages: string[];
11 | doShowDebugMessages: boolean;
12 | handleMessage: (data: string) => void;
13 | webviewContent: string;
14 | loadingIndicator: () => ReactElement;
15 | onError: (syntheticEvent: NativeSyntheticEvent) => void;
16 | onLoadEnd: () => void;
17 | onLoadStart: () => void;
18 | setWebViewRef: (ref: WebView) => void;
19 | }
20 |
21 | const WebViewLeafletView = ({
22 | backgroundColor,
23 | debugMessages,
24 | doShowDebugMessages,
25 | handleMessage,
26 | webviewContent,
27 | loadingIndicator,
28 | onError,
29 | onLoadEnd,
30 | onLoadStart,
31 | setWebViewRef
32 | }: Props) => {
33 | return (
34 |
41 | {webviewContent && (
42 | {
50 | setWebViewRef(component);
51 | }}
52 | javaScriptEnabled={true}
53 | onLoadEnd={onLoadEnd}
54 | onLoadStart={onLoadStart}
55 | onMessage={(event) => {
56 | if (event && event.nativeEvent && event.nativeEvent.data) {
57 | handleMessage(event.nativeEvent.data);
58 | }
59 | }}
60 | domStorageEnabled={true}
61 | useWebKit={true}
62 | startInLoadingState={true}
63 | onError={onError}
64 | originWhitelist={['*']}
65 | /* renderLoading={loadingIndicator || null} */
66 | source={{
67 | html: webviewContent
68 | }}
69 | allowFileAccess={true}
70 | allowUniversalAccessFromFileURLs={true}
71 | allowFileAccessFromFileURLs={true}
72 | />
73 | )}
74 |
78 |
79 | );
80 | };
81 |
82 | export default WebViewLeafletView;
83 |
--------------------------------------------------------------------------------
/WebViewLeaflet/index.d.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { WebViewError } from "react-native-webview/lib/WebViewTypes";
3 |
4 | // Type definitions for react-native-webivew-leaflet
5 | // Project: react-native-webview-leaflet
6 | // Definitions by: Reginald Johnson https://
7 |
8 | /*~ This is the module template file. You should rename it to index.d.ts
9 | *~ and place it in a folder with the same name as the module.
10 | *~ For example, if you were writing a file for "super-greeter", this
11 | *~ file should be 'super-greeter/index.d.ts'
12 | */
13 |
14 | /*~ If this module is a UMD module that exposes a global variable 'myLib' when
15 | *~ loaded outside a module loader environment, declare that global here.
16 | *~ Otherwise, delete this declaration.
17 | */
18 | export as namespace ReactNativeWebViewLeaflet;
19 |
20 | export {
21 | default as WebViewLeaflet,
22 | WebViewLeafletProps
23 | } from "./WebViewLeaflet";
24 | export {
25 | LatLng,
26 | Point,
27 | LatLngBounds,
28 | AnimationDirection,
29 | AnimationType,
30 | INFINITE_ANIMATION_ITERATIONS,
31 | WebViewLeafletEvents,
32 | MapMarkerAnimation,
33 | MapMarker,
34 | MapLayer,
35 | MapLayerType,
36 | MapShapeType,
37 | WebviewLeafletMessage
38 | } from "./models";
39 |
--------------------------------------------------------------------------------
/WebViewLeaflet/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | default as WebViewLeaflet,
3 | WebViewLeafletProps
4 | } from "./WebViewLeaflet";
5 | export {
6 | LatLng,
7 | Point,
8 | LatLngBounds,
9 | AnimationDirection,
10 | AnimationType,
11 | INFINITE_ANIMATION_ITERATIONS,
12 | WebViewLeafletEvents,
13 | MapMarkerAnimation,
14 | MapMarker,
15 | MapLayer,
16 | MapLayerType,
17 | MapShapeType,
18 | WebviewLeafletMessage
19 | } from "./models";
20 |
--------------------------------------------------------------------------------
/WebViewLeaflet/models.ts:
--------------------------------------------------------------------------------
1 | import * as ReactLeaflet from "react-leaflet";
2 | export type LatLng = ReactLeaflet.LatLng;
3 | export type Point = ReactLeaflet.Point;
4 | export type LatLngBounds = ReactLeaflet.LatLngBounds;
5 |
6 | export const OWN_POSTION_MARKER_ID = "OWN_POSTION_MARKER_ID";
7 |
8 | export enum WebViewLeafletEvents {
9 | MAP_COMPONENT_MOUNTED = "MAP_COMPONENT_MOUNTED",
10 | MAP_READY = "MAP_READY",
11 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED",
12 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED",
13 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER",
14 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED",
15 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED",
16 | ON_MOVE_END = "onMoveEnd",
17 | ON_MOVE_START = "onMoveStart",
18 | ON_MOVE = "onMove",
19 | ON_RESIZE = "onResize",
20 | ON_UNLOAD = "onUnload",
21 | ON_VIEW_RESET = "onViewReset",
22 | ON_ZOOM_END = "onZoomEnd",
23 | ON_ZOOM_LEVELS_CHANGE = "onZoomLevelsChange",
24 | ON_ZOOM_START = "onZoomStart",
25 | ON_ZOOM = "onZoom",
26 | ON_MAP_TOUCHED = "onMapClicked",
27 | ON_MAP_MARKER_CLICKED = "onMapMarkerClicked"
28 | // ON_MAP_SHAPE_CLICKED = "onMapShapeClicked" cannot click on shapes yet
29 | }
30 |
31 | export enum AnimationType {
32 | BOUNCE = "bounce",
33 | FADE = "fade",
34 | PULSE = "pulse",
35 | JUMP = "jump",
36 | SPIN = "spin",
37 | WAGGLE = "waggle"
38 | }
39 |
40 | export enum MapLayerType {
41 | IMAGE_LAYER = "ImageOverlay",
42 | TILE_LAYER = "TileLayer",
43 | VECTOR_LAYER = "VectorLayer",
44 | VIDEO_LAYER = "VideoOverlay",
45 | WMS_TILE_LAYER = "WMSTileLayer"
46 | }
47 |
48 | export enum MapShapeType {
49 | CIRCLE = "Circle",
50 | CIRCLE_MARKER = "CircleMarker",
51 | POLYLINE = "Polyline",
52 | POLYGON = "Polygon",
53 | RECTANGLE = "Rectangle"
54 | }
55 |
56 | export const INFINITE_ANIMATION_ITERATIONS: string = "infinite";
57 |
58 | export enum AnimationDirection {
59 | NORMAL = "nomal",
60 | REVERSE = "reverse",
61 | ALTERNATE = "alternate",
62 | ALTERNATE_REVERSE = "alternate-reverse"
63 | }
64 | export interface MapMarkerAnimation {
65 | type: AnimationType;
66 | duration?: number;
67 | delay?: number;
68 | direction?: AnimationDirection;
69 | iterationCount?: number | typeof INFINITE_ANIMATION_ITERATIONS;
70 | }
71 |
72 | export interface MapMarker {
73 | animation?: MapMarkerAnimation;
74 | position: LatLng;
75 | divIcon?: L.DivIcon;
76 | icon: any;
77 | iconAnchor?: Point;
78 | id?: string;
79 | size?: Point;
80 | title?: string;
81 | }
82 |
83 | export interface MapEventMessage {
84 | event?: any;
85 | msg?: string;
86 | error?: string;
87 | payload?: any;
88 | }
89 |
90 | export interface MapLayer {
91 | attribution?: string;
92 | baseLayer?: boolean;
93 | baseLayerIsChecked?: boolean;
94 | baseLayerName?: string;
95 | bounds?: LatLngBounds;
96 | id?: string;
97 | layerType?: MapLayerType;
98 | opacity?: number;
99 | pane?: string;
100 | subLayer?: string;
101 | url?: string;
102 | zIndex?: number;
103 | }
104 |
105 | export interface MapShape {
106 | bounds?: LatLng[];
107 | center?: LatLng;
108 | color?: string;
109 | id?: string;
110 | positions?: LatLng[] | LatLng[][];
111 | radius?: number;
112 | shapeType: MapShapeType;
113 | }
114 |
115 | export interface MapStartupMessage {
116 | mapLayers?: MapLayer[];
117 | mapMarkers?: MapMarker[];
118 | mapShapes?: MapShape[];
119 | mapCenterPosition?: LatLng;
120 | ownPositionMarker?: OwnPositionMarker;
121 | zoom?: number;
122 | }
123 |
124 | export type WebviewLeafletMessagePayload = {
125 | bounds?: LatLngBounds;
126 | mapCenterPosition: LatLng;
127 | mapMarkerID?: string;
128 | touchLatLng?: LatLng;
129 | zoom?: number;
130 | };
131 |
132 | export interface WebviewLeafletMessage {
133 | event?: any;
134 | msg?: string;
135 | error?: string;
136 | payload?: WebviewLeafletMessagePayload;
137 | }
138 |
139 | export type OwnPositionMarker = {
140 | animation: MapMarkerAnimation;
141 | id?: string;
142 | icon: string;
143 | position: LatLng;
144 | size: Point;
145 | };
146 |
--------------------------------------------------------------------------------
/WebViewLeaflet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-webview-leaflet",
3 | "version": "5.0.2",
4 | "description": "A React Native component that uses a React Native WebView to provide a Leaflet map.",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/reggie3/react-native-webview-leaflet.git"
8 | },
9 | "keywords": [
10 | "react-native",
11 | "leaflet",
12 | "expo",
13 | "webview"
14 | ],
15 | "main": "index.ts",
16 | "typings": "index.d.ts",
17 | "scripts": {
18 | "test": "echo \"Error: no test specified\" && exit 1"
19 | },
20 | "author": "",
21 | "license": "ISC",
22 | "dependencies": {
23 | "expo-asset": "^7.0.0",
24 | "expo-asset-utils": "^1.2.0",
25 | "expo-file-system": "^8.0.0",
26 | "leaflet": "^1.5.1",
27 | "lodash.isequal": "^4.5.0",
28 | "react-dom": "^16.10.2",
29 | "react-leaflet": "^2.6.1",
30 | "react-native-webview": "^8.0.3"
31 | },
32 | "peerDependencies": {
33 | "react": "^16.9.0",
34 | "react-leaflet": "^2.6.1",
35 | "react-native": "^0.61.2"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.0.0-0",
39 | "@babel/plugin-proposal-class-properties": "^7.5.5",
40 | "@babel/preset-react": "^7.6.3",
41 | "@types/node": "^13.1.6",
42 | "@types/react-leaflet": "^2.4.0",
43 | "babel-plugin-transform-class-properties": "^6.24.1",
44 | "gulp": "4",
45 | "gulp-run": "^1.7.1",
46 | "parcel-bundler": "^1.12.4",
47 | "parcel-plugin-inliner": "^1.0.10",
48 | "typescript": "^3.6.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reggie3/react-native-webview-leaflet/a27bc89559bc8ab470ae89b0d4faef1a2433ebb0/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reggie3/react-native-webview-leaflet/a27bc89559bc8ab470ae89b0d4faef1a2433ebb0/assets/splash.png
--------------------------------------------------------------------------------
/demoApp/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true,
3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true
4 | }
--------------------------------------------------------------------------------
/demoApp/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/demoApp/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Alert, StyleSheet, Text, View } from "react-native";
3 | import {
4 | INFINITE_ANIMATION_ITERATIONS,
5 | LatLng,
6 | WebViewLeaflet,
7 | WebViewLeafletEvents,
8 | WebviewLeafletMessage,
9 | AnimationType,
10 | MapShapeType
11 | } from "react-native-webview-leaflet";
12 | import { mapboxToken } from "./secrets";
13 | import { Button } from "native-base";
14 | import lodashSample from "lodash.sample";
15 | import * as Location from "expo-location";
16 | import * as Permissions from "expo-permissions";
17 |
18 | type LatLngObject = { lat: number; lng: number };
19 | const locations: { icon: string; position: LatLng; name: string }[] = [
20 | {
21 | icon: "⭐",
22 | position: { lat: 38.895, lng: -77.0366 },
23 | name: "Washington DC"
24 | },
25 | {
26 | icon: "🎢",
27 | position: { lat: 37.8399, lng: -77.4442 },
28 | name: "Kings Dominion"
29 | },
30 | {
31 | icon: "🎢",
32 | position: { lat: 37.23652, lng: -76.646 },
33 | name: "Busch Gardens Williamsburg"
34 | },
35 | {
36 | icon: "⚓",
37 | position: { lat: 36.8477, lng: -76.2951 },
38 | name: "USS Wisconsin (BB-64)"
39 | },
40 | {
41 | icon: "🏰",
42 | position: { lat: 28.3852, lng: -81.5639 },
43 | name: "Walt Disney World"
44 | }
45 | ];
46 |
47 | const getDuration = (): number => Math.floor(Math.random() * 3) + 1;
48 | const getDelay = (): number => Math.floor(Math.random()) * 0.5;
49 | const iterationCount = "infinite";
50 |
51 | export default function App() {
52 | const [mapCenterPosition, setMapCenterPosition] = useState({
53 | lat: 36.850769,
54 | lng: -76.285873
55 | });
56 | const [ownPosition, setOwnPosition] = useState(null);
57 | const [webViewLeafletRef, setWebViewLeafletRef] = useState(null);
58 |
59 | const onMessageReceived = (message: WebviewLeafletMessage) => {
60 | switch (message.event) {
61 | case WebViewLeafletEvents.ON_MAP_MARKER_CLICKED:
62 | Alert.alert(
63 | `Map Marker Touched, ID: ${message.payload.mapMarkerID || "unknown"}`
64 | );
65 |
66 | break;
67 | case WebViewLeafletEvents.ON_MAP_TOUCHED:
68 | const position: LatLngObject = message.payload
69 | .touchLatLng as LatLngObject;
70 | Alert.alert(`Map Touched at:`, `${position.lat}, ${position.lng}`);
71 | break;
72 | default:
73 | console.log("App received", message);
74 | }
75 | };
76 |
77 | useEffect(() => {
78 | getLocationAsync();
79 | });
80 |
81 | const getLocationAsync = async () => {
82 | let { status } = await Permissions.askAsync(Permissions.LOCATION);
83 | if (status !== "granted") {
84 | console.warn("Permission to access location was denied");
85 | }
86 |
87 | let location = await Location.getCurrentPositionAsync({});
88 | if (!ownPosition) {
89 | setOwnPosition({
90 | lat: location.coords.latitude,
91 | lng: location.coords.longitude
92 | });
93 | }
94 | };
95 |
96 | return (
97 |
98 |
99 | React Native Webview Leaflet Demo
100 |
101 |
102 | {
103 | {
105 | setWebViewLeafletRef(ref);
106 | }}
107 | backgroundColor={"green"}
108 | onMessageReceived={onMessageReceived}
109 | mapLayers={[
110 | {
111 | attribution:
112 | '© OpenStreetMap contributors',
113 | baseLayerIsChecked: true,
114 | baseLayerName: "OpenStreetMap.Mapnik",
115 | url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
116 | },
117 | {
118 | baseLayerName: "Mapbox",
119 | //url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
120 | url: `https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=${mapboxToken}`,
121 | attribution:
122 | "© OpenStreetMap contributors"
123 | }
124 | ]}
125 | mapMarkers={[
126 | ...locations.map(location => {
127 | return {
128 | id: location.name.replace(" ", "-"),
129 | position: location.position,
130 | icon: location.icon,
131 | animation: {
132 | duration: getDuration(),
133 | delay: getDelay(),
134 | iterationCount: INFINITE_ANIMATION_ITERATIONS,
135 | type: lodashSample(
136 | Object.values(AnimationType)
137 | ) as AnimationType
138 | }
139 | };
140 | }),
141 | {
142 | id: "1",
143 | position: { lat: 36.46410354, lng: -75.6432701 },
144 | icon:
145 | "https://www.catster.com/wp-content/uploads/2018/07/Savannah-cat-long-body-shot.jpg",
146 | size: [32, 32],
147 | animation: {
148 | duration: getDuration(),
149 | delay: getDelay(),
150 | iterationCount,
151 | type: AnimationType.BOUNCE
152 | }
153 | }
154 | ]}
155 | mapShapes={[
156 | {
157 | shapeType: MapShapeType.CIRCLE,
158 | color: "#123123",
159 | id: "1",
160 | center: { lat: 34.225727, lng: -77.94471 },
161 | radius: 2000
162 | },
163 | {
164 | shapeType: MapShapeType.CIRCLE_MARKER,
165 | color: "red",
166 | id: "2",
167 | center: { lat: 38.437424, lng: -78.867912 },
168 | radius: 15
169 | },
170 | {
171 | shapeType: MapShapeType.POLYGON,
172 | color: "blue",
173 | id: "3",
174 | positions: [
175 | { lat: 38.80118939192329, lng: -74.69604492187501 },
176 | { lat: 38.19502155795575, lng: -74.65209960937501 },
177 | { lat: 39.07890809706475, lng: -71.46606445312501 }
178 | ]
179 | },
180 | {
181 | shapeType: MapShapeType.POLYGON,
182 | color: "violet",
183 | id: "4",
184 | positions: [
185 | [
186 | { lat: 37.13842453422676, lng: -74.28955078125001 },
187 | { lat: 36.4433803110554, lng: -74.26208496093751 },
188 | { lat: 36.43896124085948, lng: -73.00964355468751 },
189 | { lat: 36.43896124085948, lng: -73.00964355468751 }
190 | ],
191 | [
192 | { lat: 37.505368263398104, lng: -72.38891601562501 },
193 | { lat: 37.309014074275915, lng: -71.96594238281251 },
194 | { lat: 36.69044623523481, lng: -71.87805175781251 },
195 | { lat: 36.58024660149866, lng: -72.75146484375001 },
196 | { lat: 37.36579146999664, lng: -72.88330078125001 }
197 | ]
198 | ]
199 | },
200 | {
201 | shapeType: MapShapeType.POLYLINE,
202 | color: "orange",
203 | id: "5",
204 | positions: [
205 | { lat: 35.411438052435486, lng: -78.67858886718751 },
206 | { lat: 35.9602229692967, lng: -79.18945312500001 },
207 | { lat: 35.97356075349624, lng: -78.30505371093751 }
208 | ]
209 | },
210 | {
211 | shapeType: MapShapeType.POLYLINE,
212 | color: "purple",
213 | id: "5a",
214 | positions: [
215 | [
216 | { lat: 36.36822190085111, lng: -79.26086425781251 },
217 | { lat: 36.659606226479696, lng: -79.28833007812501 },
218 | { lat: 36.721273880045004, lng: -79.81018066406251 }
219 | ],
220 | [
221 | { lat: 35.43381992014202, lng: -79.79370117187501 },
222 | { lat: 35.44277092585766, lng: -81.23840332031251 },
223 | { lat: 35.007502842952896, lng: -80.837402343750017 }
224 | ]
225 | ]
226 | },
227 | {
228 | shapeType: MapShapeType.RECTANGLE,
229 | color: "yellow",
230 | id: "6",
231 | bounds: [
232 | { lat: 36.5, lng: -75.7 },
233 | { lat: 38.01, lng: -73.13 }
234 | ]
235 | }
236 | ]}
237 | mapCenterPosition={mapCenterPosition}
238 | ownPositionMarker={
239 | ownPosition && {
240 | position: ownPosition,
241 | icon: "❤️",
242 | size: [32, 32],
243 | animation: {
244 | duration: getDuration(),
245 | delay: getDelay(),
246 | iterationCount,
247 | type: AnimationType.BOUNCE
248 | }
249 | }
250 | }
251 | zoom={7}
252 | />
253 | }
254 |
255 |
256 | {locations.map(location => {
257 | return (
258 |
268 | );
269 | })}
270 |
282 |
283 |
284 | );
285 | }
286 |
287 | const styles = StyleSheet.create({
288 | container: {
289 | flex: 1,
290 | backgroundColor: "#fff"
291 | },
292 | header: {
293 | height: 60,
294 | backgroundColor: "dodgerblue",
295 | paddingHorizontal: 10,
296 | paddingTop: 30,
297 | width: "100%"
298 | },
299 | headerText: {
300 | color: "white",
301 | fontSize: 18,
302 | fontWeight: "600"
303 | },
304 | mapControls: {
305 | backgroundColor: "rgba(255,255,255,.5)",
306 | borderRadius: 5,
307 | bottom: 25,
308 | flexDirection: "row",
309 | justifyContent: "space-between",
310 | left: 0,
311 | marginHorizontal: 10,
312 | padding: 7,
313 | position: "absolute",
314 | right: 0
315 | },
316 | mapButton: {
317 | alignItems: "center",
318 | height: 42,
319 | justifyContent: "center",
320 | width: 42
321 | },
322 | mapButtonEmoji: {
323 | fontSize: 28
324 | }
325 | });
326 |
--------------------------------------------------------------------------------
/demoApp/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-webview-leaflet-demo",
4 | "slug": "demoApp",
5 | "privacy": "public",
6 | "sdkVersion": "36.0.0",
7 | "platforms": [
8 | "ios",
9 | "android",
10 | "web"
11 | ],
12 | "version": "1.0.0",
13 | "orientation": "portrait",
14 | "icon": "./assets/icon.png",
15 | "splash": {
16 | "image": "./assets/splash.png",
17 | "resizeMode": "contain",
18 | "backgroundColor": "#ffffff"
19 | },
20 | "updates": {
21 | "fallbackToCacheTimeout": 0
22 | },
23 | "assetBundlePatterns": [
24 | "**/*"
25 | ],
26 | "ios": {
27 | "supportsTablet": true
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/demoApp/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reggie3/react-native-webview-leaflet/a27bc89559bc8ab470ae89b0d4faef1a2433ebb0/demoApp/assets/icon.png
--------------------------------------------------------------------------------
/demoApp/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reggie3/react-native-webview-leaflet/a27bc89559bc8ab470ae89b0d4faef1a2433ebb0/demoApp/assets/splash.png
--------------------------------------------------------------------------------
/demoApp/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/demoApp/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require("gulp");
2 | const clean = require("gulp-clean");
3 | const run = require("gulp-run");
4 |
5 | gulp.task("clean", function() {
6 | return gulp
7 | .src("node_modules", { allowEmpty: true, read: false })
8 | .pipe(clean());
9 | });
10 |
--------------------------------------------------------------------------------
/demoApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject",
9 | "cleanNodeModules": "del-cli --force ./node_modules",
10 | "clean": "yarn upgrade react-native-webview-leaflet"
11 | },
12 | "dependencies": {
13 | "expo": "~36.0.0",
14 | "expo-location": "^8.0.0",
15 | "expo-permissions": "^8.0.0",
16 | "lodash.sample": "^4.2.1",
17 | "native-base": "^2.13.8",
18 | "react": "~16.9.0",
19 | "react-dom": "~16.9.0",
20 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
21 | "react-native-screens": "2.0.0-alpha.12",
22 | "react-native-web": "~0.11.7",
23 | "react-native-webview-leaflet": "file:../WebViewLeaflet"
24 | },
25 | "devDependencies": {
26 | "@babel/core": "^7.0.0",
27 | "@types/react": "~16.9.0",
28 | "@types/react-native": "~0.60.23",
29 | "babel-preset-expo": "~8.0.0",
30 | "del-cli": "^3.0.0",
31 | "gulp-clean": "^0.4.0",
32 | "gulp-run": "^1.7.1",
33 | "typescript": "~3.6.3"
34 | },
35 | "private": true
36 | }
37 |
--------------------------------------------------------------------------------
/demoApp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": ["dom", "esnext"],
6 | "moduleResolution": "node",
7 | "noEmit": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "gulp": "^4.0.2",
4 | "gulp-run": "^1.7.1"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # React Native Webview Leaflet V5
2 |
3 | ## A Leaflet map component with no native code for React Native applications
4 |
5 | ### Why Use This Library
6 |
7 | This component is useful if you want to display HTML elements on an interactive map. Since the elements are standard HTML items, they can be SVG's, emojis, text, images, etc.
8 |
9 | Additionally, the elements can even be animated, updated, and changed as required.
10 |
11 | ### Why Not Use This Library
12 |
13 | You may not want to use this library if you'd rather use Google map tiles and data vice the tiles and map data from Open Street Maps.
14 |
15 | [](https://www.npmjs.com/package/react-native-webview-leaflet)
16 | [](https://www.npmjs.com/package/react-native-webview-leaflet)
17 | [](https://www.npmjs.com/package/react-native-webview-leaflet)
18 | [](https://github.com/react-native-component/react-native-webview-leaflet/blob/master/LICENSE)
19 |
20 | [](https://www.youtube.com/watch?v=Jpo-Mg3BSVk)
21 |
22 | ## Installation
23 |
24 | Install using npm or yarn like this:
25 |
26 | ```javascript
27 | npm install --save react-native-webview-leaflet
28 | ```
29 |
30 | or
31 |
32 | ```javascript
33 | yarn add react-native-webview-leaflet
34 | ```
35 |
36 | ## Usage
37 |
38 | and import like so
39 |
40 | ```javascript
41 | import WebViewLeaflet from "react-native-webview-leaflet";
42 | ```
43 |
44 | A typical example is shown below:
45 |
46 | ```javascript
47 | (this.webViewLeaflet = component)}
49 | // The rest of your props, see the list below
50 | />
51 | ```
52 |
53 | ## Props
54 |
55 | | property | required | type | purpose |
56 | | ------------------- | -------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57 | | backgroundColor | optional | string | Color seen prior to the map loading |
58 | | doShowDebugMessages | optional | boolean | show debug information from the component containing the Leaflet map |
59 | | loadingIndicator | optional | React.ReactElement | custom component displayed while the map is loading |
60 | | onError | optional | function | Will receive an error event |
61 | | onLoadEnd | optional | function | Called when map stops loading |
62 | | onLoadStart | optional | function | Called when the map starts to load |
63 | | onMessageReceived | required | function | This function receives messages in the form of a WebviewLeafletMessage object from the map |
64 | | mapLayers | optional | MapLayer array | An array of map layers |
65 | | mapMarkers | optional | MapMarker array | An array of map markers |
66 | | mapShapes | optional | MapShape[] | An array of map shapes |
67 | | mapCenterPosition | optional | {lat: [Lat], lng: [Lng]} object | The center position of the map. This coordinate will not be accurate if the map has been moved manually. However, calling the map's setMapCenterPosition function will cause the map to revert to this location |
68 | | ownPositionMarker | optional | Marker | A special marker that has an ID of OWN_POSTION_MARKER_ID | |
69 | | zoom | optional | number | Desired zoom value of the map |
70 |
71 | ### Example Marker
72 |
73 | ```javascript
74 | ownPositionMarker={{
75 | id: '1',
76 | coords: {lat: 36.00, lng, -76.00},
77 | icon: "❤️",
78 | size: [24, 24],
79 | animation: {
80 | name: AnimationType.BOUNCE,
81 | duration: ".5",
82 | delay: 0,
83 | interationCount: INFINITE_ANIMATION_ITERATIONS
84 | }
85 | }}
86 | ```
87 |
88 | After loading, the map expects to receive an array of map layer information objects. A sample object showing a [MapBox](https://www.mapbox.com/) tile layer is shown below.
89 |
90 | ```javascript
91 | {
92 | baseLayerName: 'OpenStreetMap', // the name of the layer, this will be seen in the layer selection control
93 | baseLayerIsChecked: 'true', // if the layer is selected in the layer selection control
94 | layerType: 'TileLayer', // Optional: a MapLayerType enum specifying the type of layer see "Types of Layers" below. Defaults to TILE_LAYER
95 | baseLayer: true,
96 | // url of tiles
97 | url: `https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=${mapboxToken}`,
98 | // attribution string to be shown for this layer
99 | attribution:
100 | '© OpenStreetMap contributors'
101 | }
102 | ```
103 |
104 | ### Types of Layers
105 |
106 | ```javascript
107 | export enum MapLayerType {
108 | IMAGE_LAYER = "ImageOverlay",
109 | TILE_LAYER = "TileLayer",
110 | VECTOR_LAYER = "VectorLayer",
111 | VIDEO_LAYER = "VideoOverlay",
112 | WMS_TILE_LAYER = "WMSTileLayer"
113 | }
114 | ```
115 |
116 | ### Communicating with the map
117 |
118 | #### Listening for Events
119 |
120 | This library supports map clicked, map marker clicked, and the map events that are exposed by Leaflet.
121 |
122 | ##### Leaflet Map Events
123 |
124 | The following Map Events are passed to the function designated by the onMessageReceived prop.
125 |
126 | ```javascript
127 | export enum WebViewLeafletEvents {
128 | MAP_COMPONENT_MOUNTED = "MAP_COMPONENT_MOUNTED",
129 | MAP_READY = "MAP_READY",
130 | DOCUMENT_EVENT_LISTENER_ADDED = "DOCUMENT_EVENT_LISTENER_ADDED",
131 | WINDOW_EVENT_LISTENER_ADDED = "WINDOW_EVENT_LISTENER_ADDED",
132 | UNABLE_TO_ADD_EVENT_LISTENER = "UNABLE_TO_ADD_EVENT_LISTENER",
133 | DOCUMENT_EVENT_LISTENER_REMOVED = "DOCUMENT_EVENT_LISTENER_REMOVED",
134 | WINDOW_EVENT_LISTENER_REMOVED = "WINDOW_EVENT_LISTENER_REMOVED",
135 | ON_MOVE_END = "onMoveEnd",
136 | ON_MOVE_START = "onMoveStart",
137 | ON_MOVE = "onMove",
138 | ON_RESIZE = "onResize",
139 | ON_UNLOAD = "onUnload",
140 | ON_VIEW_RESET = "onViewReset",
141 | ON_ZOOM_END = "onZoomEnd",
142 | ON_ZOOM_LEVELS_CHANGE = "onZoomLevelsChange",
143 | ON_ZOOM_START = "onZoomStart",
144 | ON_ZOOM = "onZoom",
145 | ON_MAP_TOUCHED = "onMapClicked",
146 | ON_MAP_MARKER_CLICKED = "onMapMarkerClicked"
147 | }
148 | ```
149 |
150 | Events prefixed with "ON" will receive the below object containing information about the map
151 |
152 | ```javascript
153 | {
154 | center, // center of the map
155 | bounds, // the bounds of the map
156 | zoom; // the zoom level of the map
157 | }
158 | ```
159 |
160 | ### Creating Map Markers
161 |
162 | ```javascript
163 | {
164 | id: uuidV1(), // The ID attached to the marker. It will be returned when onMarkerClicked is called
165 | position: {lat: [LATITTUDE], lng: [LONGITUDE]}, // Latitude and Longitude of the marker
166 | icon: '🍇', // HTML element that will be displayed as the marker. It can also be text or an SVG string.
167 | size: [32, 32],
168 | animation: {
169 | duration: getDuration(),
170 | delay: getDelay(),
171 | iterationCount,
172 | type: AnimationType.BOUNCE
173 | }
174 | }
175 | ```
176 |
177 | ### Adding Leaflet Geometry Layers to the Map
178 |
179 | Thanks to @gotoglup for the PR adding leaflet geometry layers. A geometry layer can be added to the may by following the example below:
180 |
181 | ```javascript
182 | mapShapes={[
183 | {
184 | shapeType: MapShapeType.CIRCLE,
185 | color: "#123123",
186 | id: "1",
187 | center: { lat: 34.225727, lng: -77.94471 },
188 | radius: 2000
189 | }
190 | ]}
191 | ```
192 |
193 | ### Available Animations
194 |
195 | Marker animations can be specified by setting the animation.type of the marker object to an AnimationType enum.
196 | Values for AnimationType can be found in the models.ts file in the WebViewLeaflet directory of this project.
197 |
198 | ### Animation Information
199 |
200 | Animations are kept in the file [markers.css](https://github.com/reggie3/react-native-webview-leaflet/blob/master/web/markers.css) They are just keyframe animations like this:
201 |
202 | ```javascript
203 | @keyframes spin {
204 | 50% {
205 | transform: rotateZ(-20deg);
206 | animation-timing-function: ease;
207 | }
208 | 100% {
209 | transform: rotateZ(360deg);
210 | }
211 | }
212 | ```
213 |
214 | ```
215 | ## Changelog
216 |
217 | ### 4.5.0
218 |
219 | * Removed Expo dependencies from the library and added polygons vectors (Thanks @gutoglup - https://github.com/gutoglup)
220 |
221 | ### 4.3.1
222 |
223 | * Fixed issue with using expo-asset-utils that prevented this package from working with iOS apps in simulator
224 |
225 | ### 4.2.0
226 |
227 | * Replace Expo dependency with expo-asset-utils
228 | * Fixed bug that caused map to not display when no ownPositionMarker was provided
229 |
230 | ### 4.1.15
231 |
232 | * Keep own position marker from being clustered
233 |
234 | ### 4.1.0
235 |
236 | * Added optional marker clustering using react-leaflet-markercluster
237 | * Update preview video in readme
238 |
239 | ### 4.0.0
240 |
241 | * Fixed map centering, and map centering button (see issue #36(https://github.com/reggie3/react-native-webview-leaflet/issues/36) )
242 | #### BREAKING CHANGES:
243 | * Center map on own current location button defaults to not being shown
244 | * Center map on own current location button requires that a "ownPositionMarker" object be passed to the WebViewLeaflet component
245 | * WebViewLeaflet component requires a "centerPosition" prop for initial centering
246 | * Map tile Layers are now passed as props to the WebViewLeaflet component
247 |
248 |
249 | ### 3.1.45
250 |
251 | * Works in production APK files.
252 | * Renders http images as map icons.
253 |
254 | ### 3.0.0
255 |
256 | * Introduced user specified tile layers.
257 |
258 | ### 2.0.0
259 |
260 | * Initial release of version 2 built on React-Leaflet
261 |
262 | ### 5.0.0
263 |
264 | * Add TypeScript support
265 | * Switch to react-native-community/react-native-webview implementation
266 | * Add ability to draw shapes on the map (Leaflet vector layers)
267 | * Display map layer vector icons
268 | * Simplify event communication
269 |
270 | ## LICENSE
271 |
272 | MIT
273 | ```
274 |
--------------------------------------------------------------------------------
/secrets.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | exports.mapboxToken = 'pk.eyJ1Ijoid2hlcmVzbXl3YXZlcyIsImEiOiJjanJ6cGZtd24xYmU0M3lxcmVhMDR2dWlqIn0.QQSWbd-riqn1U5ppmyQjRw';
4 |
--------------------------------------------------------------------------------