Add comparison app
This commit is contained in:
parent
3e51f7f57b
commit
549ba95426
7
compare-algorithms/.babelrc
Normal file
7
compare-algorithms/.babelrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": ["@babel/preset-react"],
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
||||
]
|
||||
}
|
10270
compare-algorithms/package-lock.json
generated
Normal file
10270
compare-algorithms/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
compare-algorithms/package.json
Normal file
46
compare-algorithms/package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "compare-algorithms",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode development",
|
||||
"serve": "webpack serve",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "prettier --check '**/*.js'",
|
||||
"format": "prettier --write '**/*.js'"
|
||||
},
|
||||
"author": "Alfred Melch (dev@melch.pro)",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@webpack-cli/serve": "^0.1.8",
|
||||
"babel-loader": "^8.0.6",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "^3.1.0",
|
||||
"file-loader": "^4.1.0",
|
||||
"prettier": "^1.18.2",
|
||||
"react-hot-loader": "^4.12.8",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^2.1.0",
|
||||
"webpack": "^4.36.1",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"computed-async-mobx": "^4.1.1",
|
||||
"file-drop-element": "^0.2.0",
|
||||
"leaflet": "^1.5.1",
|
||||
"mobx": "^5.13.0",
|
||||
"mobx-react": "^6.1.1",
|
||||
"mobx-utils": "^5.4.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-leaflet": "^2.4.0"
|
||||
}
|
||||
}
|
17
compare-algorithms/public/index.html
Normal file
17
compare-algorithms/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header><h1>Simplify GeoJSON</h1></header>
|
||||
<div id="map"></div>
|
||||
<main id="root"></main>
|
||||
<footer>Footer</footer>
|
||||
<script src="./bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
71
compare-algorithms/public/style.css
Normal file
71
compare-algorithms/public/style.css
Normal file
@ -0,0 +1,71 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
flex: 2;
|
||||
min-width: 300px;
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
#options {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
file-drop {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
background-color: rgba(245, 234, 174, 0.2);
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
}
|
||||
file-drop::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
border: 2px dashed #fff;
|
||||
background-color: rgba(88, 116, 88, 0.2);
|
||||
border-color: rgba(65, 129, 65, 0.5);
|
||||
border-radius: 10px;
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: all 200ms ease-in;
|
||||
transition-property: transform, opacity;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.drop-valid::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.drop-invalid::after {
|
||||
background-color: rgba(255, 27, 27, 0.2);
|
||||
border-color: rgba(255, 170, 170, 0.5);
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition-timing-function: ease-out;
|
||||
}
|
60
compare-algorithms/src/algorithms/Algorithm.js
Normal file
60
compare-algorithms/src/algorithms/Algorithm.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { computed } from 'mobx'
|
||||
|
||||
export class Algorithm {
|
||||
constructor(name, id, method) {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.method = method
|
||||
this.fields = []
|
||||
}
|
||||
|
||||
addField(fieldDesc) {
|
||||
this.fields.push(fieldDesc)
|
||||
}
|
||||
|
||||
@computed
|
||||
get params() {
|
||||
this.fields.map(f => f.value)
|
||||
}
|
||||
|
||||
async simplifyLine(coords, params) {
|
||||
return this.method(coords, ...params)
|
||||
}
|
||||
|
||||
async simplifyPolygon(coords, params) {
|
||||
return await Promise.all(
|
||||
coords.map(ring => this.simplifyLine(ring, params))
|
||||
)
|
||||
}
|
||||
|
||||
async simplifyGeoJSON(geojson, params) {
|
||||
// clone geojson
|
||||
let simplified = JSON.parse(JSON.stringify(geojson))
|
||||
for (let feature of simplified.features) {
|
||||
let geom = feature.geometry
|
||||
let coords = geom.coordinates
|
||||
switch (geom.type) {
|
||||
case 'Point':
|
||||
case 'MultiPoint':
|
||||
break
|
||||
case 'LineString':
|
||||
geom['coordinates'] = await this.simplifyLine(coords, params)
|
||||
break
|
||||
case 'MultiLineString':
|
||||
geom['coordinates'] = await Promise.all(
|
||||
coords.map(line => this.simplifyLine(line, params))
|
||||
)
|
||||
break
|
||||
case 'Polygon':
|
||||
geom['coordinates'] = await this.simplifyPolygon(coords, params)
|
||||
break
|
||||
case 'MultiPolygon':
|
||||
geom['coordinates'] = await Promise.all(
|
||||
coords.map(poly => this.simplifyPolygon(poly, params))
|
||||
)
|
||||
}
|
||||
}
|
||||
return simplified
|
||||
}
|
||||
simplifyTopoJSON(data, params) {}
|
||||
}
|
32
compare-algorithms/src/algorithms/Field.js
Normal file
32
compare-algorithms/src/algorithms/Field.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { observable } from 'mobx'
|
||||
|
||||
class Field {
|
||||
@observable
|
||||
value
|
||||
|
||||
constructor(name, id, type, props, initialValue) {
|
||||
this.name = name
|
||||
this.id = id
|
||||
this.type = type
|
||||
this.props = props || {}
|
||||
this.value = initialValue
|
||||
}
|
||||
}
|
||||
|
||||
export class Range extends Field {
|
||||
constructor(name, id, min, max, step, value) {
|
||||
super(name, id, 'range', { min, max, step }, value)
|
||||
}
|
||||
}
|
||||
|
||||
export class Checkbox extends Field {
|
||||
constructor(name, id, checked) {
|
||||
super(name, id, 'checkbox', {}, checked)
|
||||
}
|
||||
}
|
||||
|
||||
export class ToleranceRange extends Range {
|
||||
constructor(name = 'Tolerance', id = 'tol') {
|
||||
super(name, id, 0.01, 1, 0.01, 0.2)
|
||||
}
|
||||
}
|
115
compare-algorithms/src/algorithms/index.js
Normal file
115
compare-algorithms/src/algorithms/index.js
Normal file
@ -0,0 +1,115 @@
|
||||
import {
|
||||
simplify_nth_point,
|
||||
simplify_radial_distance,
|
||||
simplify_perpendicular_distance,
|
||||
simplify_reumann_witkam,
|
||||
simplify_opheim,
|
||||
simplify_lang,
|
||||
simplify_douglas_peucker,
|
||||
simplify_douglas_peucker_n
|
||||
} from '../../../lib/psimpl-js/index.js'
|
||||
import SimplifyJS from '../../../lib/simplify-js-alternative/simplify.js'
|
||||
import { simplifyWasm } from '../../../lib/simplify-wasm/index.js'
|
||||
|
||||
import { Range, ToleranceRange, Checkbox } from './Field.js'
|
||||
import { Algorithm } from './Algorithm.js'
|
||||
|
||||
class NthPoint extends Algorithm {
|
||||
constructor() {
|
||||
super('Nth Point', 'nth_point', simplify_nth_point)
|
||||
this.addField(new Range('n', 'n', 1, 15, 1, 3))
|
||||
}
|
||||
}
|
||||
|
||||
class RadialDistance extends Algorithm {
|
||||
constructor() {
|
||||
super('RadialDistance', 'rad_dist', simplify_radial_distance)
|
||||
this.addField(new ToleranceRange())
|
||||
}
|
||||
}
|
||||
|
||||
class PerpendicularDistance extends Algorithm {
|
||||
constructor() {
|
||||
super(
|
||||
'Perpendicular Distance',
|
||||
'perp_dist',
|
||||
simplify_perpendicular_distance
|
||||
)
|
||||
this.addField(new ToleranceRange())
|
||||
this.addField(new Range('Repeat', 'repeat', 1, 10, 1, 3))
|
||||
}
|
||||
}
|
||||
|
||||
class ReumannWitkam extends Algorithm {
|
||||
constructor() {
|
||||
super('Reuman-Witkam Algorithm', 'reumann_witkam', simplify_reumann_witkam)
|
||||
this.addField(new ToleranceRange())
|
||||
}
|
||||
}
|
||||
|
||||
class Opheim extends Algorithm {
|
||||
constructor() {
|
||||
super('Opheim Algorithm', 'opheim', simplify_opheim)
|
||||
this.addField(new ToleranceRange('Minimum Tolerance', 'minTol'))
|
||||
this.addField(new ToleranceRange('Maximum Tolerance', 'maxTol'))
|
||||
}
|
||||
}
|
||||
|
||||
class Lang extends Algorithm {
|
||||
constructor() {
|
||||
super('Lang Algorithm', 'lang', simplify_lang)
|
||||
this.addField(new ToleranceRange())
|
||||
this.addField(new Range('Look ahead', 'lookAhead', 2, 100, 1, 5))
|
||||
}
|
||||
}
|
||||
|
||||
class DouglasPeucker extends Algorithm {
|
||||
constructor() {
|
||||
super(
|
||||
'Douglas Peucker Algorithm',
|
||||
'dougles_peucker',
|
||||
simplify_douglas_peucker
|
||||
)
|
||||
this.addField(new ToleranceRange())
|
||||
}
|
||||
}
|
||||
|
||||
class DouglasPeuckerAlt extends Algorithm {
|
||||
constructor() {
|
||||
super(
|
||||
'Douglas Peucker Alt. Algorithm',
|
||||
'dougles_peucker_n',
|
||||
simplify_douglas_peucker_n
|
||||
)
|
||||
this.addField(new Range('Count', 'count', 1, 2000, 1, 1000))
|
||||
}
|
||||
}
|
||||
|
||||
class SimplifyJSAlgo extends Algorithm {
|
||||
constructor() {
|
||||
super('Simplify.js', 'simplify_js', SimplifyJS)
|
||||
this.addField(new ToleranceRange())
|
||||
this.addField(new Checkbox('High Quality', 'highQual', false))
|
||||
}
|
||||
}
|
||||
|
||||
class SimplifyWASMAlgo extends Algorithm {
|
||||
constructor() {
|
||||
super('Simplify.wasm', 'simplify_wasm', simplifyWasm)
|
||||
this.addField(new ToleranceRange())
|
||||
this.addField(new Checkbox('High Quality', 'highQual', true))
|
||||
}
|
||||
}
|
||||
|
||||
export default [
|
||||
new NthPoint(),
|
||||
new RadialDistance(),
|
||||
new PerpendicularDistance(),
|
||||
new ReumannWitkam(),
|
||||
new Opheim(),
|
||||
new Lang(),
|
||||
new DouglasPeucker(),
|
||||
new DouglasPeuckerAlt(),
|
||||
new SimplifyJSAlgo(),
|
||||
new SimplifyWASMAlgo()
|
||||
]
|
38
compare-algorithms/src/components/DataSelector.js
Normal file
38
compare-algorithms/src/components/DataSelector.js
Normal file
@ -0,0 +1,38 @@
|
||||
import 'file-drop-element'
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
|
||||
import state from '../state'
|
||||
|
||||
async function readFile(file) {
|
||||
return new Promise(resolve => {
|
||||
let reader = new FileReader()
|
||||
reader.readAsText(file)
|
||||
reader.onload = function() {
|
||||
resolve(this.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DataSelector extends React.Component {
|
||||
componentDidMount() {
|
||||
let drop = document.getElementsByTagName('file-drop')[0]
|
||||
drop.addEventListener('filedrop', e => {
|
||||
let file = e.files[0]
|
||||
state.originalName = file.name
|
||||
readFile(file).then(text => {
|
||||
state.originalText = text
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h2>Data</h2>
|
||||
<file-drop accept="application/geo+json">Drop GeoJSON here!</file-drop>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
41
compare-algorithms/src/components/LayerControl.js
Normal file
41
compare-algorithms/src/components/LayerControl.js
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
|
||||
import state from '../state'
|
||||
|
||||
@observer
|
||||
export class LayerControl extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h3>Layers</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
<input
|
||||
name="originalActive"
|
||||
type="checkbox"
|
||||
checked={state.originalActive}
|
||||
onChange={() => (state.originalActive = !state.originalActive)}
|
||||
></input>
|
||||
Original: {state.originalName}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input
|
||||
name="simplifiedActive"
|
||||
type="checkbox"
|
||||
checked={state.simplifiedActive}
|
||||
onChange={() =>
|
||||
(state.simplifiedActive = !state.simplifiedActive)
|
||||
}
|
||||
></input>
|
||||
Simplified
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
38
compare-algorithms/src/components/MyMap.js
Normal file
38
compare-algorithms/src/components/MyMap.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
import { Map, TileLayer, GeoJSON } from 'react-leaflet'
|
||||
import { observer } from 'mobx-react'
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
|
||||
import state from '../state.js'
|
||||
|
||||
const tileLayers = {
|
||||
osmDE: (
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png"
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
/>
|
||||
),
|
||||
osmMapnik: (
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@observer
|
||||
export class MyMap extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Map center={[0, 0]} zoom={2}>
|
||||
{tileLayers[state.tileLayer]}
|
||||
{state.originalActive && (
|
||||
<GeoJSON key={state.originalText} data={state.original} />
|
||||
)}
|
||||
{state.simplifiedActive && (
|
||||
<GeoJSON key={state.simplifiedText} data={state.simplified} />
|
||||
)}
|
||||
</Map>
|
||||
)
|
||||
}
|
||||
}
|
9
compare-algorithms/src/components/RangeInput.css
Normal file
9
compare-algorithms/src/components/RangeInput.css
Normal file
@ -0,0 +1,9 @@
|
||||
.range-input label {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.range-input input {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/3;
|
||||
}
|
67
compare-algorithms/src/components/RangeInput.js
Normal file
67
compare-algorithms/src/components/RangeInput.js
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
|
||||
import './RangeInput.css'
|
||||
|
||||
export class RangeInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
rangeValue: props.value
|
||||
? this.indexOfValue(props.value)
|
||||
: Math.floor((this.length - 1) / 2)
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
}
|
||||
|
||||
get options() {
|
||||
let { min, max, step } = this.props
|
||||
let arr = []
|
||||
if (step <= 0 || max < min) return []
|
||||
for (min; min <= max; min += step) {
|
||||
arr.push(min)
|
||||
}
|
||||
return arr.map(val => Math.round(val * 1000) / 1000)
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.options.length
|
||||
}
|
||||
|
||||
get realValue() {
|
||||
return this.options[this.state.rangeValue]
|
||||
}
|
||||
|
||||
indexOfValue(value) {
|
||||
let i = 0
|
||||
for (let opt of this.options) {
|
||||
if (opt >= value) return i
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ rangeValue: e.target.value })
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(this.options[e.target.value])
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="range-input">
|
||||
<label>
|
||||
<span>{this.props.label ? this.props.label : this.props.name}:</span>
|
||||
<span>{Number(this.realValue.toFixed(2))}</span>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={this.length - 1}
|
||||
value={this.state.rangeValue}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
73
compare-algorithms/src/components/SimplificationControl.js
Normal file
73
compare-algorithms/src/components/SimplificationControl.js
Normal file
@ -0,0 +1,73 @@
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
|
||||
import algorithms from '../algorithms/index.js'
|
||||
import { RangeInput } from './RangeInput.js'
|
||||
|
||||
import state from '../state.js'
|
||||
|
||||
const FieldComponent = ({ field }) => {
|
||||
switch (field.type) {
|
||||
case 'range':
|
||||
let { name, id } = field
|
||||
return (
|
||||
<RangeInput
|
||||
name={name}
|
||||
label={id}
|
||||
{...field.props}
|
||||
value={field.value}
|
||||
onChange={val => (field.value = val)}
|
||||
/>
|
||||
)
|
||||
case 'checkbox':
|
||||
return (
|
||||
<>
|
||||
{field.name}
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={e => (field.value = e.target.checked)}
|
||||
checked={field.value}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
default:
|
||||
return <span>Error: Field type {field.type} not known.</span>
|
||||
}
|
||||
}
|
||||
|
||||
const Options = ({ fields }) => {
|
||||
return (
|
||||
<fieldset>
|
||||
{fields.map((field, i) => (
|
||||
<FieldComponent key={i} field={field} />
|
||||
))}
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
@observer
|
||||
export class SimplificationControl extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h3>Simplification</h3>
|
||||
<select
|
||||
name="algorithm"
|
||||
value={state.algorithmId}
|
||||
onChange={e => (state.algorithmId = e.target.value)}
|
||||
>
|
||||
{algorithms.map((algo, i) => (
|
||||
<option key={i} value={algo.id}>
|
||||
{algo.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{algorithms.map((algo, i) => (
|
||||
<div key={i} hidden={!(algo === state.selectedAlgorithm)}>
|
||||
<Options fields={algo.fields} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
29
compare-algorithms/src/components/TileLayerControl.css
Normal file
29
compare-algorithms/src/components/TileLayerControl.css
Normal file
@ -0,0 +1,29 @@
|
||||
fieldset.radio-grp {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* .radio-grp input[type='radio'] {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
.radio-grp input[type='radio']:focus + label {
|
||||
outline: 1px;
|
||||
}
|
||||
|
||||
.radio-grp label {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
border-radius: 2px;
|
||||
background-color: #ddd;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.radio-grp label.selected {
|
||||
background-color: rgb(175, 243, 255);
|
||||
border-color: rgb(75, 174, 240);
|
||||
}
|
||||
|
||||
.radio-grp label:hover {
|
||||
background-color: rgb(144, 208, 250);
|
||||
}
|
47
compare-algorithms/src/components/TileLayerControl.js
Normal file
47
compare-algorithms/src/components/TileLayerControl.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
|
||||
import state from '../state.js'
|
||||
import './TileLayerControl.css'
|
||||
|
||||
const RadioButton = ({ option, selected, fullName }) => {
|
||||
return (
|
||||
<label className={option === selected ? 'selected' : ''}>
|
||||
<input
|
||||
type="radio"
|
||||
name="tileLayer"
|
||||
value={option}
|
||||
checked={option === selected}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
{fullName ? fullName : option}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TileLayerControl extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h3>Background Layer</h3>
|
||||
<fieldset
|
||||
className="radio-grp"
|
||||
onChange={e => (state.tileLayer = e.target.value)}
|
||||
// onChange={console.log}
|
||||
>
|
||||
<RadioButton
|
||||
option="osmDE"
|
||||
selected={state.tileLayer}
|
||||
fullName="OpenStreetMap DE"
|
||||
/>
|
||||
<RadioButton
|
||||
option="osmMapnik"
|
||||
selected={state.tileLayer}
|
||||
fullName="OpenStreetMap Mapnik"
|
||||
/>
|
||||
</fieldset>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
27
compare-algorithms/src/index.js
Normal file
27
compare-algorithms/src/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { Component } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
import { TileLayerControl } from './components/TileLayerControl.js'
|
||||
import { DataSelector } from './components/DataSelector.js'
|
||||
import { LayerControl } from './components/LayerControl.js'
|
||||
import { SimplificationControl } from './components/SimplificationControl.js'
|
||||
import { MyMap } from './components/MyMap.js'
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<MyMap />
|
||||
<div id="options">
|
||||
<h2>Options</h2>
|
||||
<TileLayerControl />
|
||||
<DataSelector />
|
||||
<LayerControl />
|
||||
<SimplificationControl />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
46
compare-algorithms/src/state.js
Normal file
46
compare-algorithms/src/state.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { observable, computed } from 'mobx'
|
||||
import { asyncComputed } from 'computed-async-mobx'
|
||||
|
||||
import algorithms from './algorithms/index.js'
|
||||
|
||||
class State {
|
||||
@observable tileLayer = 'osmDE'
|
||||
@observable originalActive = true
|
||||
@observable simplifiedActive = true
|
||||
|
||||
@observable algorithmId = 'nth_point'
|
||||
@observable topoJSON = false
|
||||
@computed
|
||||
get selectedAlgorithm() {
|
||||
return algorithms.filter(algo => algo.id === this.algorithmId)[0]
|
||||
}
|
||||
@computed
|
||||
get algoParams() {
|
||||
return this.selectedAlgorithm.fields.map(f => f.value)
|
||||
}
|
||||
|
||||
@observable originalText = '{"type": "FeatureCollection", "features": []}'
|
||||
@observable originalName = ''
|
||||
@computed
|
||||
get original() {
|
||||
return JSON.parse(this.originalText)
|
||||
}
|
||||
|
||||
simplifiedPromise = asyncComputed(this.original, 500, async () => {
|
||||
return await this.selectedAlgorithm.simplifyGeoJSON(
|
||||
this.original,
|
||||
this.algoParams
|
||||
)
|
||||
})
|
||||
|
||||
@computed
|
||||
get simplified() {
|
||||
return this.simplifiedPromise.get()
|
||||
}
|
||||
@computed
|
||||
get simplifiedText() {
|
||||
return JSON.stringify(this.simplified)
|
||||
}
|
||||
}
|
||||
|
||||
export default new State()
|
46
compare-algorithms/webpack.config.js
Normal file
46
compare-algorithms/webpack.config.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
|
||||
const CopyPlugin = require('copy-webpack-plugin')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loaders: ['babel-loader'],
|
||||
exclude: [/node_modules/, /data/]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: ['url-loader']
|
||||
},
|
||||
{
|
||||
test: /\.wasm$/,
|
||||
type: 'javascript/auto',
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[hash:5].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [new CleanWebpackPlugin(), new CopyPlugin(['public'])],
|
||||
devtool: 'source-map',
|
||||
// to make webpack work with emscripten js files
|
||||
target: 'web',
|
||||
node: {
|
||||
__dirname: false,
|
||||
fs: 'empty',
|
||||
Buffer: false,
|
||||
process: false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user