Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ public/bundle.js.map
.bundle.css
npm-debug.*
client-config.json
.idea
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ Example config:
"bounds": [19.105224609375, 59.645540251443215, 27.88330078125, 65.84776766596988],
"center": [24.805, 60.0888]
}
],
"tracks": [
{
"type": "daily-trackserver",
"address": "<url to track server>",
"vesselId": "<vessel id on track server>",
"username": "basic auth user",
"password": "basic auth passwd"
},
{ // Or for testing purposes
"type": "dummy-trackserver"
}
]
}
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"marine",
"signalk-webapp"
],
"dependencies": {},
"devDependencies": {
"@mapbox/mbtiles": "0.9.0",
"autoprefixer": "7.1.1",
Expand All @@ -41,8 +40,9 @@
"baret": "1.0.3",
"classnames": "2.2.5",
"compression": "1.6.2",
"d3-scale": "^1.0.6",
"express": "4.15.3",
"geolib": "2.0.22",
"geolib": "^2.0.22",
"leaflet": "1.1.0",
"leaflet-rotatedmarker": "0.2.0",
"less": "2.7.2",
Expand Down
4 changes: 4 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
type: "signalk",
address: window.location.protocol + "//" + window.location.host
}],
tracks: [{
type: "signalk",
address: window.location.protocol + "//" + window.location.host
}]
};
</script>
<div id="app"></div>
Expand Down
14 changes: 11 additions & 3 deletions src/client/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ function parseJSON(response) {
return response.json()
}

function get({url}) {
const request = fetch(url)
function get({url, basicAuth}) {
const request = fetch(url, basicAuth ? basicAuthHeaders(basicAuth) : undefined)
.then(checkStatus)
.then(parseJSON)
return Bacon.fromPromise(request)
}

function basicAuthHeaders({username, password}) {
return {
headers: {
'Authorization': 'Basic '+ btoa(`${username}:${password}`),
}
}
}

module.exports = {
get
}
}
10 changes: 6 additions & 4 deletions src/client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Bacon from 'baconjs'
import _ from 'lodash'
import {COG, HDG, MAX_ZOOM, MIN_ZOOM, EXTENSION_LINE_OFF, EXTENSION_LINE_2_MIN, EXTENSION_LINE_5_MIN, EXTENSION_LINE_10_MIN} from './enums'
import Map from './map'
import Connection from './data-connection'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring code from of the master branch inside a feature branch should be avoided. It's a PITA when it come to merge multiple branches. Refactoring process are bumpy enough to worth it's own branch :D.

import DataConnection from './data-connection'
import TrackConnection from './track-connection'
import {toDegrees, toNauticalMiles} from './utils'
import InstrumentConfig from './instrument-config'
import fullscreen from './fullscreen'
Expand All @@ -18,7 +19,8 @@ numeral.nullFormat('N/A')

const drawObject = Atom({distance: 0, del: false})

const connection = Connection({providers: settings.get().data, settings})
const dataConnection = DataConnection({providers: settings.get().data, settings})
const trackConnection = TrackConnection(settings.get().tracks)

fullscreen(settings)

Expand Down Expand Up @@ -315,12 +317,12 @@ const App = (
<div>
<Controls settings={settings}/>
<Menu settings={settings}/>
<Instruments settings={settings} data={connection.selfData}/>
<Instruments settings={settings} data={dataConnection.selfData}/>
{settings.view(L.prop('loadingChartProviders')).skipDuplicates().map(loading => {
if (loading) {
return <div className='charts-loading map-wrapper'><h2>Loading ...</h2></div>
} else {
return <Map connection={connection} settings={settings} drawObject={drawObject} />
return <Map dataConnection={dataConnection} trackConnection={trackConnection} settings={settings} drawObject={drawObject} />
}
})}
</div>
Expand Down
125 changes: 121 additions & 4 deletions src/client/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import classNames from 'classnames'
import _ from 'lodash'
import numeral from 'numeral'
import * as Leaf from 'leaflet'
import {scaleLinear} from 'd3-scale'
import {computeDestinationPoint} from 'geolib'
import LeafletRotatedMarker from 'leaflet-rotatedmarker'
import api from './api'
Expand All @@ -13,7 +14,7 @@ import {COG, HDG, MAX_ZOOM, MIN_ZOOM, KNOTS_TO_MS, EXTENSION_LINE_OFF, EXTENSION

class Map extends React.Component {
componentDidMount() {
initMap(this.props.connection, this.props.settings, this.props.drawObject)
initMap(this.props.dataConnection, this.props.trackConnection, this.props.settings, this.props.drawObject)
}
render() {
const {settings} = this.props
Expand All @@ -26,7 +27,7 @@ class Map extends React.Component {
}
}

function initMap(connection, settings, drawObject) {
function initMap(dataConnection, trackConnection, settings, drawObject) {
console.log('Init map')
const initialSettings = settings.get()
const map = Leaf.map('map', {
Expand All @@ -52,7 +53,7 @@ function initMap(connection, settings, drawObject) {
pointer.addTo(map)

const vesselData = Bacon.combineTemplate({
vesselData: connection.selfData,
vesselData: dataConnection.selfData,
settings
})
vesselData.onValue(({vesselData, settings}) => {
Expand Down Expand Up @@ -86,11 +87,12 @@ function initMap(connection, settings, drawObject) {
}
})

handleAisTargets({map, aisData: connection.aisData, settings})
handleAisTargets({map, aisData: dataConnection.aisData, settings})
handleDrawPath({map, settings, drawObject})
handleMapZoom()
handleDragAndFollow()
handleInstrumentsToggle()
showTracksOnMapMove()
function handleMapZoom() {
settings.map('.zoom').skipDuplicates().onValue(zoom => {
map.setZoom(zoom)
Expand Down Expand Up @@ -133,6 +135,28 @@ function initMap(connection, settings, drawObject) {
map.invalidateSize(true)
})
}


function showTracksOnMapMove () {
const paths = ['navigation.speedOverGround', 'environment.depth.belowTransducer']
Bacon.fromEvent(map, 'moveend')
.map(() => map.getBounds())
.skipDuplicates(_.isEqual)
.flatMapLatest(bounds => {
return trackConnection.queryTracks(bounds, paths)
})
.onValue(tracks => {
const units = Bacon.combineAsArray(
paths.map(path => trackConnection.getUnits(path).mapError((err) => "n/a"))
)
units.onValue(units => {
renderTracks(map, tracks, paths, units)
})
units.onError((err) => {
console.log(err)
})
})
}
}

function handleAisTargets({map, aisData, settings}) {
Expand Down Expand Up @@ -257,6 +281,99 @@ function addCharts(map, providers) {
})
}

let trackLayers = []
const trackColors = [
"#4c79a6",
"#46a65b",
"#a262a6",
]

function renderTracks (map, featureCollection, paths, units) {
trackLayers.forEach(layer => map.removeLayer(layer))
trackLayers = featureCollection.features.reduce((acc, feature, i) => {
const dayIndex = feature.properties.starttime
? (new Date(feature.properties.starttime).getTime() / 86400000).toFixed()
: 0
const basicStyle = {
color: trackColors[dayIndex % trackColors.length],
stroke: 8
}
const geoJSONLayer = Leaf.geoJSON(feature, { style: basicStyle })
geoJSONLayer.on('click', e =>
geoJSONLayer.setStyle(_.assign({}, basicStyle, { weight: 6 }))
)
acc.push(geoJSONLayer)

featureCollection.properties.dataPaths && featureCollection.properties.dataPaths.forEach((path, i) => {
acc.push(toDataLayer(feature, path, units[i], i, featureCollection.properties.dataPaths.length))
})
return acc
}, [])
trackLayers.forEach(layer => layer.addTo(map))
}




function toDataLayer (feature, path, unit, pathIndex, pathsCount) {
const valueColor = scaleLinear()
.domain([2, 5, 10, 25])
.range(['red', 'yellow', 'green', 'blue'])
const circleStyles = [
{
radius: 4,
fillOpacity: 1,
fillColor: valueColor,
color: () => '#000',
opacity: 0
},
{
radius: 8,
fillOpacity: 0,
fillColor: () => '#000',
color: valueColor,
opacity: 0.6
}
]

const points = []

const circleStyle =
circleStyles[(pathIndex + pathsCount - 1) % circleStyles.length]
// [lat, lon, elev, timestamp, ...]
const dataIndex = pathIndex + 4
feature.geometry.coordinates.forEach(line => {
line.forEach(coordinates => {
if (coordinates.length >= dataIndex) {
const circleMarker = Leaf.circleMarker(
Leaf.latLng(coordinates[1], coordinates[0]),
{
radius: circleStyle.radius,
weight: 5,
fillOpacity: circleStyle.fillOpacity,
fillColor: circleStyle.fillColor(coordinates[dataIndex]),
color: circleStyle.color(coordinates[dataIndex]),
opacity: circleStyle.opacity
}
)
const displayValue = coordinates[dataIndex] ? coordinates[dataIndex].toFixed(2) : ""
circleMarker.bindPopup(
`<dl><dt>${path}</dt><dd>${displayValue} ${unit}</dd></dl><i>${new Date(coordinates[3])}</i>`
)
circleMarker.on('mouseover', e => {
circleMarker.openPopup()
})

points.push(circleMarker)
}
})
})
return Leaf.layerGroup(points)
}




function addBasemap(map) {
map.createPane('basemap')
const basemapStyle = {
Expand Down
17 changes: 17 additions & 0 deletions src/client/provider-daily-trackserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import api from './api'

function initialize({address, vesselId, username, password}) {
return {
queryTracks
}

function queryTracks(bbox) {
return api.get({
url: `${address}/${vesselId}/daily-tracks?bbox=${bbox.toBBoxString()}`,
basicAuth: !!username && !!password ? {username, password} : undefined
})
}
}


module.exports = initialize
85 changes: 85 additions & 0 deletions src/client/provider-dummy-trackserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Bacon from 'baconjs'

function initialize() {
return {
queryTracks
}

function queryTracks() {
return Bacon.once([{
"date": "2015-09-12T21:00:00.000Z",
"route": {
"coordinates": dummyTrack,
"type": "LineString"
}
}])
}
}

module.exports = initialize


const dummyTrack = [
[24.92908833, 60.16096],
[24.92874333, 60.16108333],
[24.92775667, 60.16093667],
[24.92695333, 60.16033],
[24.92484333, 60.15942],
[24.92451833, 60.15845],
[24.92474833, 60.15717167],
[24.92463167, 60.15614167],
[24.923115, 60.15368833],
[24.91990167, 60.15086333],
[24.91928333, 60.15006333],
[24.91914333, 60.14961833],
[24.91963333, 60.148935],
[24.91681333, 60.14681333],
[24.91295667, 60.14484167],
[24.911985, 60.14418667],
[24.91068833, 60.14295167],
[24.90756333, 60.13916],
[24.90702333, 60.13768833],
[24.90621667, 60.12825333],
[24.90563, 60.12466167],
[24.90499667, 60.122085],
[24.90439333, 60.11776333],
[24.903615, 60.11370167],
[24.90346833, 60.11191833],
[24.90361833, 60.111485],
[24.90347, 60.11123333],
[24.89799167, 60.10720333],
[24.89496333, 60.10476667],
[24.886915, 60.09952167],
[24.881305, 60.09628833],
[24.87474167, 60.09287],
[24.87222833, 60.092015],
[24.86859, 60.09202833],
[24.86418833, 60.09156333],
[24.861075, 60.09156833],
[24.85545667, 60.09123333],
[24.82868167, 60.092265],
[24.82256, 60.093525],
[24.80822833, 60.09555333],
[24.806815, 60.09742667],
[24.80533833, 60.09978167],
[24.804705, 60.10182167],
[24.80423833, 60.10438667],
[24.80378167, 60.10836],
[24.802325, 60.11174167],
[24.80118167, 60.11354333],
[24.80063333, 60.11473833],
[24.79798, 60.11574833],
[24.79440667, 60.11672167],
[24.78841667, 60.117675],
[24.78689167, 60.11805833],
[24.786635, 60.11830667],
[24.78680333, 60.118935],
[24.78824, 60.12085833],
[24.79051333, 60.12318333],
[24.79388667, 60.12534667],
[24.796865, 60.12681667],
[24.800175, 60.12871],
[24.801405, 60.12992333],
[24.80265667, 60.13165667],
[24.80341833, 60.13354167]
]
Loading