terra-draw-route-snap-mode 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +3 -0
- package/CNAME +1 -0
- package/LICENSE.txt +8 -0
- package/README.md +66 -0
- package/demo/assets/favicon.ico +0 -0
- package/demo/assets/icons/android-chrome-192x192.png +0 -0
- package/demo/assets/icons/android-chrome-512x512.png +0 -0
- package/demo/assets/icons/apple-touch-icon.png +0 -0
- package/demo/assets/icons/favicon-16x16.png +0 -0
- package/demo/assets/icons/favicon-32x32.png +0 -0
- package/demo/assets/icons/mstile-150x150.png +0 -0
- package/demo/assets/imgs/geolocation.png +0 -0
- package/demo/assets/imgs/github.png +0 -0
- package/demo/assets/imgs/logo.png +0 -0
- package/demo/components/app.tsx +18 -0
- package/demo/components/geojson-tab/GeoJSONTab.tsx +42 -0
- package/demo/components/geojson-tab/style.module.css +30 -0
- package/demo/components/geojson-tab/useDownloadJSON.ts +34 -0
- package/demo/components/header/Header.tsx +25 -0
- package/demo/components/header/style.module.css +78 -0
- package/demo/components/info-tab/InfoTab.tsx +101 -0
- package/demo/components/info-tab/style.module.css +42 -0
- package/demo/components/map-button/ClearButton.tsx +24 -0
- package/demo/components/map-button/MapButton.tsx +41 -0
- package/demo/components/map-button/style.module.css +42 -0
- package/demo/components/map-buttons/MapButtons.tsx +28 -0
- package/demo/components/map-buttons/style.module.css +17 -0
- package/demo/declaration.d.ts +9 -0
- package/demo/index.html +23 -0
- package/demo/index.tsx +12 -0
- package/demo/manifest.json +21 -0
- package/demo/package-lock.json +18900 -0
- package/demo/package.json +50 -0
- package/demo/public/network.json +216459 -0
- package/demo/routes/home/colors.ts +31 -0
- package/demo/routes/home/index.tsx +101 -0
- package/demo/routes/home/setup-draw.ts +67 -0
- package/demo/routes/home/setup-leaflet.ts +41 -0
- package/demo/routes/home/setup-routing.ts +21 -0
- package/demo/routes/home/style.module.css +94 -0
- package/demo/size-plugin.json +1 -0
- package/demo/style/index.css +20 -0
- package/demo/sw.js +4 -0
- package/demo/template.html +30 -0
- package/demo/tsconfig.json +7 -0
- package/demo/utils/casing.ts +7 -0
- package/demo/utils/dates.ts +10 -0
- package/demo/utils/geo.ts +4 -0
- package/demo/vite.config.js +10 -0
- package/dist/kdbush/geokdbush.d.ts +3 -0
- package/dist/kdbush/kdbush.d.ts +16 -0
- package/dist/kdbush/tinyqueue.d.ts +11 -0
- package/dist/routing.d.ts +37 -0
- package/dist/terra-draw-route-snap-mode.cjs +2 -0
- package/dist/terra-draw-route-snap-mode.cjs.map +1 -0
- package/dist/terra-draw-route-snap-mode.d.ts +75 -0
- package/dist/terra-draw-route-snap-mode.modern.js +2 -0
- package/dist/terra-draw-route-snap-mode.modern.js.map +1 -0
- package/dist/terra-draw-route-snap-mode.module.js +2 -0
- package/dist/terra-draw-route-snap-mode.module.js.map +1 -0
- package/dist/terra-draw-route-snap-mode.umd.js +2 -0
- package/dist/terra-draw-route-snap-mode.umd.js.map +1 -0
- package/docs/assets/favicon-r77Z2In5.ico +0 -0
- package/docs/assets/index-5ax2eNro.js +4 -0
- package/docs/assets/index-CEAM8jr3.css +1 -0
- package/docs/assets/index-Cfqr5nmq.js +4 -0
- package/docs/assets/index-CpmDVghy.css +1 -0
- package/docs/assets/index-D8NK55L4.js +4 -0
- package/docs/assets/index-DJYewHY6.js +4 -0
- package/docs/assets/index-DT3pkFX6.js +4 -0
- package/docs/assets/index-DZ8OkOfD.js +4 -0
- package/docs/assets/index-DjN8PkFw.js +4 -0
- package/docs/assets/index-iILsBcOs.js +4 -0
- package/docs/assets/logo-DQvm3LRv.png +0 -0
- package/docs/index.html +24 -0
- package/docs/network.json +216459 -0
- package/eslint.config.mjs +54 -0
- package/jest.config.js +5 -0
- package/package.json +97 -0
- package/src/kdbush/geokdbush.ts +183 -0
- package/src/kdbush/kdbush.ts +189 -0
- package/src/kdbush/tinyqueue.ts +108 -0
- package/src/routing.spec.ts +91 -0
- package/src/routing.ts +115 -0
- package/src/terra-draw-route-snap-mode.ts +462 -0
- package/src/terra-draw-route-snap.mode.spec.ts +24 -0
- package/tsconfig.json +12 -0
package/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
terradraw.io
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2025 James Milner
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Terra Draw Route Snapping Mode
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This repository is for the `TerraDrawRouteSnappingMode` module. `TerraDrawRouteSnappingMode` is designed to help with the scenario where you want to be able to create a multi-stop route on a map, snapping the route against a predefined route network. This is achieved by client side routing on a FeatureCollection<LineString> where the LineStrings have identical coordinates.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```shell
|
|
9
|
+
npm install terra-draw-route-snap-mode
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
We can import `TerraDrawRouteSnapMode` in this way:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { TerraDrawRouteSnapMode } from terra-draw-route-snap-mode
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
We can construct `TerraDrawRouteSnapMode` like so:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
new TerraDrawRouteSnapMode({
|
|
24
|
+
routing,
|
|
25
|
+
maxPoints: 5,
|
|
26
|
+
styles: {
|
|
27
|
+
lineStringColor: '#990000',
|
|
28
|
+
routePointColor: '#990000'
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Where routing is of type:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export interface RoutingInterface {
|
|
37
|
+
getRoute: (startCoord: Position, endCoord: Position) => Feature<LineString> | null;
|
|
38
|
+
getClosestNetworkCoordinate: (coordinate: Position) => Position | null;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You could construct the `routing` like so:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Routing } from "../../../src/terra-draw-route-snap-mode";
|
|
46
|
+
import { FeatureCollection, LineString } from "geojson";
|
|
47
|
+
import { TerraRoute, createCheapRuler } from 'terra-route';
|
|
48
|
+
|
|
49
|
+
// Initialise the TerraRoute instance with the default distance
|
|
50
|
+
const terraRoute = new TerraRoute()
|
|
51
|
+
|
|
52
|
+
// Construct the route network ready to call `getRoute` on terraRoute later in the TerraDrawRouteSnapMode instance
|
|
53
|
+
terraRoute.buildRouteGraph(network)
|
|
54
|
+
|
|
55
|
+
const terraRouting = new Routing({
|
|
56
|
+
network,
|
|
57
|
+
useCache: true,
|
|
58
|
+
routeFinder: terraRoute
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If you want to see the faster CheapRuler implementation from the `terra-route` package, please see the demo folder in the `setup-routing.ts` file for an examples. CheapRuler is a reasonable distance metric to use for network graphs of less than 500km size that are not located on or near the poles.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
// import { Route, Router, CustomHistory } from "preact-router";
|
|
3
|
+
// import { createHashHistory } from "history";
|
|
4
|
+
import Header from "./header/Header";
|
|
5
|
+
|
|
6
|
+
// Code-splitting is automated for `routes` directory
|
|
7
|
+
import Home from "../routes/home";
|
|
8
|
+
|
|
9
|
+
console.log('App.tsx');
|
|
10
|
+
|
|
11
|
+
const App = () => (
|
|
12
|
+
<div id="app">
|
|
13
|
+
<Header />
|
|
14
|
+
<Home />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default App;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import style from "./style.module.css";
|
|
3
|
+
import { useMemo } from "preact/hooks";
|
|
4
|
+
import { GeoJSONStoreFeatures } from "../../node_modules/terra-draw/dist/store/store";
|
|
5
|
+
import { useDownloadJSON } from "./useDownloadJSON";
|
|
6
|
+
import { fileDate } from "../../utils/dates";
|
|
7
|
+
|
|
8
|
+
const GeoJSONTab = ({ features }: { features: GeoJSONStoreFeatures[] }) => {
|
|
9
|
+
// Create a FeatureCollection when features changes
|
|
10
|
+
const featureCollection = useMemo(
|
|
11
|
+
() => ({
|
|
12
|
+
type: "FeatureCollection",
|
|
13
|
+
features,
|
|
14
|
+
}),
|
|
15
|
+
[features]
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Turn it into a string so it can be rendered,
|
|
19
|
+
// again only when features change
|
|
20
|
+
const featureCollectionString = useMemo(() => {
|
|
21
|
+
return JSON.stringify(featureCollection, null, 4);
|
|
22
|
+
}, [featureCollection]);
|
|
23
|
+
|
|
24
|
+
const downloadJSON = useDownloadJSON();
|
|
25
|
+
|
|
26
|
+
// Download to a file called terradraw.geojson
|
|
27
|
+
const downloadGeoJSON = () =>
|
|
28
|
+
downloadJSON(featureCollection, `terradraw_${fileDate()}.geojson`);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div class={style.container}>
|
|
32
|
+
<textarea class={style.geojson} readonly>
|
|
33
|
+
{featureCollectionString}
|
|
34
|
+
</textarea>
|
|
35
|
+
<button class={style.download} onClick={downloadGeoJSON}>
|
|
36
|
+
Download GeoJSON
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default GeoJSONTab;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.geojson {
|
|
2
|
+
resize: none;
|
|
3
|
+
width: 100%;
|
|
4
|
+
flex-grow: 1;
|
|
5
|
+
height: calc(100vh - 193px);
|
|
6
|
+
/** accomidate the 3px margin top for the download button **/
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.container {
|
|
10
|
+
padding: 20px;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-grow: 1;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.download {
|
|
17
|
+
padding: 10px;
|
|
18
|
+
font-weight: bold;
|
|
19
|
+
background: #00d088;
|
|
20
|
+
color: white;
|
|
21
|
+
border: 0;
|
|
22
|
+
border: 2px solid rgba(0, 0, 0, 0.2);
|
|
23
|
+
margin-top: 3px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.download:hover {
|
|
27
|
+
background: #00d088c4;
|
|
28
|
+
border: 2px solid rgba(0, 0, 0, 0.2);
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
export function useDownloadJSON() {
|
|
4
|
+
const downloadJSON = useCallback(
|
|
5
|
+
(json: Record<string, any>, filename: string) => {
|
|
6
|
+
// Turn the JSON object into a string
|
|
7
|
+
const data = JSON.stringify(json, null, 4);
|
|
8
|
+
|
|
9
|
+
// Pass the string to a Blob and turn it
|
|
10
|
+
// into an ObjectURL
|
|
11
|
+
const blob = new Blob([data], { type: "text/plain" });
|
|
12
|
+
const jsonObjectUrl = URL.createObjectURL(blob);
|
|
13
|
+
|
|
14
|
+
// Create an anchor element, set it's
|
|
15
|
+
// href to be the Object URL we have created
|
|
16
|
+
// and set the download property to be the file name
|
|
17
|
+
// we want to set
|
|
18
|
+
const anchorEl = document.createElement("a");
|
|
19
|
+
anchorEl.href = jsonObjectUrl;
|
|
20
|
+
anchorEl.download = filename;
|
|
21
|
+
|
|
22
|
+
// There is no need to actually attach the DOM
|
|
23
|
+
// element but we do need to click on it
|
|
24
|
+
anchorEl.click();
|
|
25
|
+
|
|
26
|
+
// We don't want to keep a reference to the file
|
|
27
|
+
// any longer so we release it manually
|
|
28
|
+
URL.revokeObjectURL(jsonObjectUrl);
|
|
29
|
+
},
|
|
30
|
+
[]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return downloadJSON;
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import { Link } from "preact-router/match";
|
|
3
|
+
import style from "./style.module.css";
|
|
4
|
+
import logo from "../../assets/imgs/logo.png";
|
|
5
|
+
import github from "../../assets/imgs/github.png";
|
|
6
|
+
|
|
7
|
+
const Header = () => (
|
|
8
|
+
<header class={style.header}>
|
|
9
|
+
<div class={style.nav}>
|
|
10
|
+
<img class={style.logo} src={logo} />
|
|
11
|
+
<nav>
|
|
12
|
+
<Link activeClassName={style.active} href="/">
|
|
13
|
+
Home
|
|
14
|
+
</Link>
|
|
15
|
+
</nav>
|
|
16
|
+
</div>
|
|
17
|
+
<div class={style.github}>
|
|
18
|
+
<a href="https://www.github.com/JamesLMilner/terra-draw">
|
|
19
|
+
<img src={github} />
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export default Header;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 70px;
|
|
4
|
+
padding: 0;
|
|
5
|
+
background: #fdfdfd;
|
|
6
|
+
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
|
7
|
+
z-index: 50;
|
|
8
|
+
display: flex;
|
|
9
|
+
color: #565656;
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.header .nav {
|
|
15
|
+
width: 100%;
|
|
16
|
+
display: flex;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.github {
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-items: center;
|
|
22
|
+
align-items: center;
|
|
23
|
+
margin-right: 20px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.logo {
|
|
27
|
+
height: 35px;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-self: center;
|
|
30
|
+
padding-left: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.header h1 {
|
|
34
|
+
float: left;
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0 15px;
|
|
37
|
+
font-size: 24px;
|
|
38
|
+
line-height: 70px;
|
|
39
|
+
font-weight: 400;
|
|
40
|
+
color: #fdfdfd;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.header nav {
|
|
44
|
+
font-size: 100%;
|
|
45
|
+
margin-left: 20px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.header nav a {
|
|
49
|
+
display: inline-block;
|
|
50
|
+
height: 70px;
|
|
51
|
+
line-height: 70px;
|
|
52
|
+
padding: 0 15px;
|
|
53
|
+
min-width: 50px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
background: fdfdfd;
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
color: #565656;
|
|
58
|
+
will-change: background-color;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.header nav a:hover,
|
|
62
|
+
.header nav a:active {
|
|
63
|
+
border-bottom: solid 5px #00d088;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.header nav a.active {
|
|
67
|
+
border-bottom: solid 5px #00d088;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media screen and (max-width: 500px) {
|
|
71
|
+
.logo {
|
|
72
|
+
display: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.header nav {
|
|
76
|
+
margin-left: 0px;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import style from "./style.module.css";
|
|
3
|
+
import { getHHMMSS } from "../../utils/dates";
|
|
4
|
+
import { area, length } from "../../utils/geo";
|
|
5
|
+
import { useMemo } from "preact/hooks";
|
|
6
|
+
import { GeoJSONStoreFeatures } from "../../node_modules/terra-draw/dist/store/store";
|
|
7
|
+
|
|
8
|
+
const InfoTab = ({
|
|
9
|
+
selected,
|
|
10
|
+
features,
|
|
11
|
+
}: {
|
|
12
|
+
selected: undefined | GeoJSONStoreFeatures;
|
|
13
|
+
features: GeoJSONStoreFeatures[];
|
|
14
|
+
}) => {
|
|
15
|
+
const { points, lines, polygons } = useMemo(() => {
|
|
16
|
+
const points: GeoJSONStoreFeatures[] = [];
|
|
17
|
+
const lines: GeoJSONStoreFeatures[] = [];
|
|
18
|
+
const polygons: GeoJSONStoreFeatures[] = [];
|
|
19
|
+
features.forEach((f) => {
|
|
20
|
+
if (f.geometry.type === "Point") {
|
|
21
|
+
points.push(f);
|
|
22
|
+
}
|
|
23
|
+
if (f.geometry.type === "LineString") {
|
|
24
|
+
lines.push(f);
|
|
25
|
+
}
|
|
26
|
+
if (f.geometry.type === "Polygon") {
|
|
27
|
+
polygons.push(f);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return { points, lines, polygons };
|
|
32
|
+
}, [features]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div class={style.container}>
|
|
36
|
+
<div class={style.all}>
|
|
37
|
+
<h3 class={style.header}> All Features </h3>
|
|
38
|
+
<span class={style.row}>
|
|
39
|
+
<span class={style.type}>Total</span> {features.length}
|
|
40
|
+
</span>
|
|
41
|
+
<span class={style.row}>
|
|
42
|
+
<span class={style.type}>Polygons:</span>
|
|
43
|
+
{polygons.length}
|
|
44
|
+
</span>
|
|
45
|
+
<span class={style.row}>
|
|
46
|
+
<span class={style.type}>LineStrings:</span>
|
|
47
|
+
{lines.length}
|
|
48
|
+
</span>
|
|
49
|
+
<span class={style.row}>
|
|
50
|
+
<span class={style.type}>Points:</span>
|
|
51
|
+
{points.length}
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div class={style.current}>
|
|
55
|
+
<h3 class={style.header}> Selected Feature </h3>
|
|
56
|
+
<span class={style.row}>
|
|
57
|
+
<span class={style.type}>ID</span>
|
|
58
|
+
{selected ? (selected.id as string).slice(0, 8) + "..." : "N/A"}
|
|
59
|
+
</span>
|
|
60
|
+
<span class={style.row}>
|
|
61
|
+
<span class={style.type}>Geometry Type</span>
|
|
62
|
+
{selected ? selected.geometry.type : "N/A"}
|
|
63
|
+
</span>
|
|
64
|
+
<span class={style.row}>
|
|
65
|
+
<span class={style.type}>Created</span>
|
|
66
|
+
{selected
|
|
67
|
+
? getHHMMSS(selected.properties.createdAt as number)
|
|
68
|
+
: "N/A"}
|
|
69
|
+
</span>
|
|
70
|
+
<span class={style.row}>
|
|
71
|
+
<span class={style.type}>Updated</span>
|
|
72
|
+
{selected
|
|
73
|
+
? getHHMMSS(selected.properties.updatedAt as number)
|
|
74
|
+
: "N/A"}
|
|
75
|
+
</span>
|
|
76
|
+
<span class={style.row}>
|
|
77
|
+
<span class={style.type}>Coordinates</span>
|
|
78
|
+
{selected && selected.geometry.type === "Polygon"
|
|
79
|
+
? selected.geometry.coordinates[0].length
|
|
80
|
+
: selected && selected.geometry.type === "LineString"
|
|
81
|
+
? selected.geometry.coordinates.length
|
|
82
|
+
: "N/A"}
|
|
83
|
+
</span>
|
|
84
|
+
<span class={style.row}>
|
|
85
|
+
<span class={style.type}>Area (m2)</span>
|
|
86
|
+
{selected && selected.geometry.type === "Polygon"
|
|
87
|
+
? area(selected).toFixed(2)
|
|
88
|
+
: "N/A"}
|
|
89
|
+
</span>
|
|
90
|
+
<span class={style.row}>
|
|
91
|
+
<span class={style.type}>Length (km)</span>
|
|
92
|
+
{selected && selected.geometry.type === "LineString"
|
|
93
|
+
? length(selected).toFixed(2)
|
|
94
|
+
: "N/A"}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default InfoTab;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.current,
|
|
2
|
+
.all {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
border-radius: 20px;
|
|
6
|
+
border: solid 3px #565656;
|
|
7
|
+
padding: 20px;
|
|
8
|
+
margin-bottom: 20px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.header {
|
|
12
|
+
margin-top: 0;
|
|
13
|
+
font-weight: bolder;
|
|
14
|
+
font-size: 20px;
|
|
15
|
+
text-transform: capitalize;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.type {
|
|
19
|
+
display: inline-block;
|
|
20
|
+
width: 150px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
button:hover {
|
|
24
|
+
background: white;
|
|
25
|
+
border: solid 3px #222222;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.container {
|
|
29
|
+
width: 100%;
|
|
30
|
+
padding: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.row {
|
|
34
|
+
width: 100%;
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
border-bottom: 1px solid #d9d9d9;
|
|
38
|
+
margin-bottom: 10px;
|
|
39
|
+
white-space: nowrap;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import style from "./style.module.css";
|
|
3
|
+
|
|
4
|
+
const ClearButton = ({
|
|
5
|
+
label,
|
|
6
|
+
onClick,
|
|
7
|
+
}: {
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
label?: string;
|
|
10
|
+
}) => {
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
id={'clear'}
|
|
14
|
+
class={style.button}
|
|
15
|
+
onClick={() => {
|
|
16
|
+
onClick()
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
{label}
|
|
20
|
+
</button>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default ClearButton;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import style from "./style.module.css";
|
|
3
|
+
import { titleCase } from "../../utils/casing";
|
|
4
|
+
|
|
5
|
+
const MapButton = ({
|
|
6
|
+
mode,
|
|
7
|
+
currentMode,
|
|
8
|
+
changeMode,
|
|
9
|
+
label,
|
|
10
|
+
hiddenOnTouch,
|
|
11
|
+
}: {
|
|
12
|
+
mode: string;
|
|
13
|
+
currentMode: string;
|
|
14
|
+
changeMode: (mode: string) => void;
|
|
15
|
+
label?: string;
|
|
16
|
+
hiddenOnTouch?: boolean;
|
|
17
|
+
}) => {
|
|
18
|
+
let classes = style.button;
|
|
19
|
+
|
|
20
|
+
if (hiddenOnTouch) {
|
|
21
|
+
classes = `${style.hiddenOnMobile} ${classes}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (currentMode === mode) {
|
|
25
|
+
classes = `${style.active} ${classes}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
id={mode}
|
|
31
|
+
class={classes}
|
|
32
|
+
onClick={() => {
|
|
33
|
+
changeMode(mode);
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{label ? label : titleCase(mode)}
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default MapButton;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.button {
|
|
2
|
+
padding: 10px;
|
|
3
|
+
width: 115px;
|
|
4
|
+
background: #fdfdfd;
|
|
5
|
+
border: solid 1px #ebebeb;
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
border-radius: 10px;
|
|
8
|
+
font-weight: bold;
|
|
9
|
+
font-size: 16px;
|
|
10
|
+
color: #565656;
|
|
11
|
+
margin: 4px;
|
|
12
|
+
margin-top: 8px;
|
|
13
|
+
border: solid 3px #565656;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.button:hover {
|
|
17
|
+
background: white;
|
|
18
|
+
border: solid 3px #222222;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.active {
|
|
22
|
+
color: #02cf87;
|
|
23
|
+
transition: color 0.5s;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.hiddenOnMobile {
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media screen and (max-width: 900px) {
|
|
30
|
+
.expanded,
|
|
31
|
+
.collapsed {
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.button {
|
|
36
|
+
padding: 9px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.hiddenOnMobile {
|
|
40
|
+
display: none;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import MapButton from "../map-button/MapButton";
|
|
2
|
+
import ClearButton from "../map-button/ClearButton";
|
|
3
|
+
import { h } from "preact";
|
|
4
|
+
import style from "./style.module.css";
|
|
5
|
+
|
|
6
|
+
const MapButtons = ({
|
|
7
|
+
mode,
|
|
8
|
+
onClear,
|
|
9
|
+
changeMode,
|
|
10
|
+
}: {
|
|
11
|
+
mode: string;
|
|
12
|
+
changeMode: (mode: string) => void;
|
|
13
|
+
onClear: () => void;
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<div class={style.buttons}>
|
|
17
|
+
<MapButton
|
|
18
|
+
label={"Route Snap"}
|
|
19
|
+
mode={"routesnap"}
|
|
20
|
+
currentMode={mode}
|
|
21
|
+
changeMode={changeMode}
|
|
22
|
+
/>
|
|
23
|
+
<ClearButton onClick={onClear} label='Clear' />
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default MapButtons;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.buttons {
|
|
2
|
+
position: relative;
|
|
3
|
+
top: 0;
|
|
4
|
+
z-index: 1000;
|
|
5
|
+
margin: auto;
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media screen and (max-width: 900px) {
|
|
12
|
+
.buttons {
|
|
13
|
+
width: 85%;
|
|
14
|
+
margin: auto;
|
|
15
|
+
margin-left: 45px;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
|
|
7
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
8
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
9
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet" />
|
|
13
|
+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
|
|
14
|
+
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
|
|
15
|
+
<title>Terra Draw - route snap mode</title>
|
|
16
|
+
</head>
|
|
17
|
+
|
|
18
|
+
<body>
|
|
19
|
+
<div id="app"></div>
|
|
20
|
+
<script prerender type="module" src="index.tsx"></script>
|
|
21
|
+
</body>
|
|
22
|
+
|
|
23
|
+
</html>
|