Skip to content

Commit

Permalink
Merge branch 'feature/custom-area' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
dehmer committed Jun 23, 2019
2 parents a9ca7e6 + b655218 commit 24443b4
Show file tree
Hide file tree
Showing 19 changed files with 581 additions and 60 deletions.
Binary file added doc/MIG-Annex-F-MOR-List-v3.1.14.xlsx
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "odin-c2is",
"version": "0.2.4",
"version": "0.2.5",
"description": "ODIN Command and Control Information System",
"main": "dist/main.js",
"scripts": {
Expand Down
9 changes: 7 additions & 2 deletions src/main/menu/file-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ const menu = {
click: sendMessage('COMMAND_NEW_POI')
},
{
label: 'Area of Interest',
click: sendMessage('COMMAND_NEW_AOI'),
label: 'Named Area of Interest',
click: sendMessage('COMMAND_NEW_NAI'),
enabled: true
},
{
label: 'Target Area of Interest',
click: sendMessage('COMMAND_NEW_TAI'),
enabled: true
},
{ type: 'separator' },
Expand Down
105 changes: 105 additions & 0 deletions src/renderer/components/AOIProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react'
import { Paper, TextField } from '@material-ui/core'
import { withStyles } from '@material-ui/core/styles'
import PropTypes from 'prop-types'
import store from '../stores/poi-store'
import selection from './App.selection'

class AOIProperties extends React.Component {
constructor (props) {
super(props)

// FIXME: provide all properties to that we don't have to query store
// Must set name if undefined in order for name TextField to be controlled.
const aoi = store.state()[props.uuid]
if (!aoi.name) aoi.name = ''
this.state = { ...aoi }
}

handleNameChange (value) {
store.rename(this.props.uuid, value)
this.setState({ ...this.state, name: value })
}

handleKeyDown (event) {
switch (event.key) {
case 'Escape': return selection.deselect()
case 'Enter':
if (event.target.tagName !== 'TEXTAREA') selection.deselect()
break
}
}

handleCommentChange (value) {
this.setState({ ...this.state, comment: value })
}

handleCommentBlur (value) {
store.comment(this.props.uuid, value)
}

render () {
const { name, comment, hidden } = this.state
const style = {
display: hidden ? 'none' : 'grid'
}

return (
<Paper
className={ this.props.classes.paper }
style={ style }
elevation={ 4 }
onKeyDown={ event => this.handleKeyDown(event) }
>
<TextField
className={ this.props.classes.name }
label={ 'Name' }
value={ name }
onChange={ event => this.handleNameChange(event.target.value) }
/>
<TextField
className={ this.props.classes.comment }
label={ 'Comment '}
value={ comment }
multiline
onChange={ event => this.handleCommentChange(event.target.value) }
onBlur={ event => this.handleCommentBlur(event.target.value) }
>
</TextField>
</Paper>
)
}
}

const styles = theme => ({
paper: {
padding: theme.spacing.unit * 4,
height: 'auto',
pointerEvents: 'auto',
gridArea: 'R',
background: 'rgba(252, 252, 255, 0.9)',

display: 'grid',
gridTemplateRows: 'max-content max-content max-content',
gridTemplatecolumns: 'auto auto',
gridGap: '2em',
gridTemplateAreas: `
"name name"
"comment comment"
`
},
name: {
gridArea: 'name'
},
comment: {
gridArea: 'comment',
overflow: 'auto'
}
})

AOIProperties.propTypes = {
classes: PropTypes.any.isRequired,
uuid: PropTypes.any.isRequired
}

export default withStyles(styles)(AOIProperties)
5 changes: 3 additions & 2 deletions src/renderer/components/ipc/ipc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { COMMAND_ADJUST, COMMAND_RESET_FILTERS } from './display-filters'
import { COMMAND_MAP_TILE_PROVIDER, COMMAND_HIDPI_SUPPORT, COMMAND_TOGGLE_MAP_VISIBILITY } from './tile-provider'
import { COMMAND_COPY_COORDS } from './clipboard'
import { COMMAND_NEW_POI, COMMAND_NEW_AOI } from './poi'
import { COMMAND_NEW_POI, COMMAND_NEW_NAI, COMMAND_NEW_TAI } from './poi'
import { COMMAND_IMPORT_LAYER } from './layer'

export default {
Expand All @@ -12,6 +12,7 @@ export default {
COMMAND_COPY_COORDS,
COMMAND_TOGGLE_MAP_VISIBILITY,
COMMAND_NEW_POI,
COMMAND_NEW_AOI,
COMMAND_NEW_NAI,
COMMAND_NEW_TAI,
COMMAND_IMPORT_LAYER
}
7 changes: 5 additions & 2 deletions src/renderer/components/ipc/poi.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const COMMAND_NEW_POI = () => () => {
})
}

export const COMMAND_NEW_AOI = ({ map }) => () => {
const area = sidc => ({ map }) => () => {

const options = {
tooltips: false,
Expand All @@ -21,9 +21,12 @@ export const COMMAND_NEW_AOI = ({ map }) => () => {
}

map.once('pm:create', ({ layer }) => {
store.add(uuid(), { latlngs: layer._latlngs })
store.add(uuid(), { latlngs: layer._latlngs, sidc })
layer.remove(map)
})

map.pm.enableDraw('Polygon', options)
}

export const COMMAND_NEW_NAI = area('GFGPSAN--------')
export const COMMAND_NEW_TAI = area('GFGPSAT--------')
4 changes: 2 additions & 2 deletions src/renderer/components/ipc/tile-provider.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import L from 'leaflet'
import Leaflet from '../../leaflet'
import { layers } from '../../leaflet/index'
import settings from '../../model/settings'

const defautTileProvider = {
Expand All @@ -10,7 +10,7 @@ const defautTileProvider = {
attribution: `&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors`
}

const removeTileLayers = map => Leaflet.layers(map)
const removeTileLayers = map => layers(map)
.filter(layer => layer instanceof L.TileLayer)
.forEach(layer => map.removeLayer(layer))

Expand Down
10 changes: 2 additions & 8 deletions src/renderer/components/map/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ import { withStyles } from '@material-ui/core/styles'
import PropTypes from 'prop-types'
import { ipcRenderer } from 'electron'
import evented from '../../evented'
import 'leaflet/dist/leaflet.css'
import 'leaflet.pm'
import 'leaflet.pm/dist/leaflet.pm.css'
import './leaflet-icons'
import { panes } from '../../leaflet/index'
import { K } from '../../../shared/combinators'
import Leaflet from '../../leaflet'
import '../../layers/L.GeoJSON.Symbols'
import { zoomLevels } from './zoom-levels'
import { defaultValues } from '../ipc/display-filters'
import { tileProvider } from '../ipc/tile-provider'
Expand All @@ -36,8 +31,7 @@ const updateDisplayFilter = map => values => {
.map(([name, { value, unit }]) => `${name}(${value}${unit})`)
.join(' ')

Leaflet
.panes(layer => layer /* instanceof L.TileLayer */)(map)
panes(layer => layer /* instanceof L.TileLayer */)(map)
.map(pane => pane.style)
.forEach(style => (style.filter = filter))
}
Expand Down
10 changes: 8 additions & 2 deletions src/renderer/layers/poi-layer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import POIProperties from '../components/POIProperties'
import AOIProperties from '../components/AOIProperties'
import L from 'leaflet'
import uuid from 'uuid-random'
import evented from '../evented'
Expand Down Expand Up @@ -36,16 +37,21 @@ const poi = (id, properties) => {
}

const aoi = (id, properties) => {

// Default to PENETRATION BOX (no labeling at all)
const sidc = properties.sidc || 'GFGPOAP--------'

return {
type: 'Feature',
id: id,
geometry: geometry(properties),
properties: { t: properties.name, sidc: 'GFGPSAN--------' }, // NAI
properties: { t: properties.name, sidc }, // NAI
actions: {
update: store.update(id),
properties: () => store.state()[id],
paste: properties => store.add(uuid(), properties),
delete: () => store.remove(id)
delete: () => store.remove(id),
edit: () => <AOIProperties uuid={ id } />
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/layers/symbol-layer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import L from 'leaflet'
import Leaflet from '../leaflet'
import { layers } from '../leaflet/index'
import evented from '../evented'
import store from '../stores/layer-store'

Expand Down Expand Up @@ -59,7 +59,7 @@ const fitBounds = bounds => {
map.fitBounds(bounds, { animate: true })
}

const removeLayer = name => Leaflet.layers(group)
const removeLayer = name => layers(group)
.filter(layer => layer.options.id === name)
.forEach(layer => layer.remove())

Expand Down
23 changes: 0 additions & 23 deletions src/renderer/leaflet.js

This file was deleted.

93 changes: 93 additions & 0 deletions src/renderer/leaflet/L.Area.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import L from 'leaflet'
import { K } from '../../shared/combinators'

L.Area = L.Polygon.extend({
initialize (latlngs, options) {
L.setOptions(this, options)
L.Polygon.prototype.initialize.call(this, latlngs, options)
},

onAdd (map) {
L.Polygon.prototype.onAdd.call(this, map)

this.label = K(L.SVG.create('text'))(text => {
const lines = []
if (this.options.type) lines.push(this.options.type)
if (this.options.t) lines.push(this.options.t)

const point = this.centerOfMass()
text.textContent = lines[0]
text.setAttribute('x', point[0])
text.setAttribute('y', point[1])
text.setAttribute('text-anchor', 'middle')

lines.splice(1).forEach(line => {
const tspan = L.SVG.create('tspan')
tspan.textContent = line
tspan.setAttribute('text-anchor', 'middle')
tspan.setAttribute('dy', '1.2em')
tspan.setAttribute('x', point[0])
text.appendChild(tspan)
})

const group = this._path.parentElement
group.appendChild(text)
})

this.updateLabelPosition()
},

onRemove (map) {
const group = this._path.parentElement
if (this.label) group.removeChild(this.label)
L.Polygon.prototype.onRemove.call(this, map)
},

redraw () {
L.Polygon.prototype.redraw.call(this)
this.updateLabelPosition()
},

updateLabelPosition () {
if (!this.label) return
const point = this.centerOfMass()
this.label.setAttribute('x', point[0])
this.label.setAttribute('y', point[1])

Array.from(this.label.childNodes)
.splice(1)
.forEach(child => child.setAttribute('x', point[0]))
},

_project () {
L.Polygon.prototype._project.call(this)
this.updateLabelPosition()
},

/**
* Optimization.
* Mostly Polygon.getCenter() without layerPointToLatLng().
*
* @returns layer point instead of latlng
*/
centerOfMass () {
const points = this._rings[0]
const len = points.length
if (!len) return null

// polygon centroid algorithm; only uses the first ring if there are multiple
let area = 0
let x = 0
let y = 0

for (let i = 0, j = len - 1; i < len; j = i++) {
const f = points[i].y * points[j].x - points[j].y * points[i].x
x += (points[i].x + points[j].x) * f
y += (points[i].y + points[j].y) * f
area += f * 3
}

if (area === 0) return [points[0].x, points[0].y]
else return [x / area, y / area]
}
})
Loading

0 comments on commit 24443b4

Please sign in to comment.