Skip to content

Commit

Permalink
Merge pull request #12 from mapcomponents/fix/MlMarkerLayer-new-Data
Browse files Browse the repository at this point in the history
Fix/ml marker layer new data
  • Loading branch information
MartinAlzueta authored Jul 8, 2024
2 parents 53c814a + af747c5 commit 4057a2a
Show file tree
Hide file tree
Showing 6 changed files with 988 additions and 119 deletions.
118 changes: 94 additions & 24 deletions src/components/MlIconLayer/MlIconLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ import { MapboxLayer } from "@deck.gl/mapbox";
import { IconLayer } from "@deck.gl/layers";

import Airplane from "./assets/airplane-icon.png";
import Ships from "./assets/Ships_v2.png";
import { Divider } from "@mui/material";

const navStats = {
0: "under way using engine",
1: "at anchor",
2: "not under command",
3: "restricted maneuverability",
4: "constrained by her draught",
5: "moored",
6: "aground",
7: "engaged in fishing",
8: "under way sailing",
9: "reserved for future amendment of navigational status for ships carrying DG, HS, or MP, or IMO hazard or pollutant category C, high speed craft (HSC)",
10: "reserved for future amendment of navigational status for ships carrying dangerous goods (DG), harmful substances (HS) or marine pollutants (MP), or IMO hazard or pollutant category A, wing in ground (WIG)",
11: "power-driven vessel towing astern (regional use)",
12: "power-driven vessel pushing ahead or towing alongside (regional use)",
13: "reserved for future use",
14: "AIS-SART (active), MOB-AIS, EPIRB-AIS",
15: "default",
};

const MlIconLayer = (props) => {
// Use a useRef hook to reference the layer object to be able to access it later inside useEffect hooks
Expand All @@ -28,6 +49,27 @@ const MlIconLayer = (props) => {

const [hoverInfo, setHoverInfo] = useState({});

const [vesselInfo, setVesselInfo] = useState();

const getVesselInfo = (mmsi) => {
fetch("https://meri.digitraffic.fi/api/ais/v1/vessels/" + mmsi)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
setVesselInfo(data);
})
.catch((error) => {
console.error(
"There has been a problem with your fetch operation:",
error
);
});
};

const startAnimation = () => {
if (timer.current) {
timer.current.stop();
Expand All @@ -51,33 +93,35 @@ const MlIconLayer = (props) => {
type: IconLayer,
data,
pickable: true,
iconAtlas: Airplane,
iconAtlas: Ships,
iconMapping: {
airplane: {
moving: {
x: 0,
y: 0,
width: 512,
height: 512,
},
blue_airplane: {
other: {
x: 512,
y: 0,
width: 512,
height: 512,
},
},
sizeScale: 20,
sizeScale: 30,
autoHighlight: true,
onHover: (d) => {
if (d.picked) {
setVesselInfo(undefined);
setHoverInfo(d);
} else {
setHoverInfo({});
}
},
getPosition: (d) => [d.longitude, d.latitude],
onClick: (ev) => getVesselInfo(ev.object.mmsi),
getIcon: (d) => {
return d.origin_country === "Germany" ? "blue_airplane" : "airplane";
return d.navStat === 0 ? "moving" : "other";
},
getAngle: (d) => -d.true_track,
};
Expand All @@ -87,16 +131,22 @@ const MlIconLayer = (props) => {
if (!simpleDataContext.data) return;
let airplanes_tmp = rawDataRef.current;
let _timeNow = new Date().getTime();

airplanes_tmp = airplanes_tmp.map((d) => {
const [longitude, latitude] = d.interpolatePos(
(_timeNow - d.time_contact * 1000)/1000 / fetchEverySeconds
);
let trackPorcentage =
(_timeNow - d.time_contact) / 1000 / fetchEverySeconds;
if (trackPorcentage > 1) {
trackPorcentage = 1;
}

const [longitude, latitude] = d.interpolatePos(trackPorcentage);
return {
...d,
longitude,
latitude,
};
});

currentFrame.current += 1;
setData(airplanes_tmp);
};
Expand Down Expand Up @@ -180,7 +230,6 @@ const MlIconLayer = (props) => {

function renderTooltip(info) {
let { object, x, y } = info;

if (!object) {
return null;
}
Expand All @@ -190,7 +239,6 @@ const MlIconLayer = (props) => {
className="tooltip"
style={{
zIndex: 1000,

position: "fixed",
padding: "8px",
borderRadius: "4px",
Expand All @@ -200,36 +248,58 @@ const MlIconLayer = (props) => {
opacity: 1,
left: x,
top: y,
minWidth: "180px",
marginTop: "20px",
marginLeft: "20px",
display: "flex",
}}
>
<div style={{ paddingRight: "10px" }}>
Callsign:
<div style={{ paddingRight: "10px"}}>
<b>MMSI:</b>
{object.mmsi}
<br />
{object.altitude && (
<>
<b>Navigational Status:</b>
<br />
{object.navStat}: {navStats[object.navStat]}
<br />
</>
{object.origin_country && (
<>
Altitude:
Country:
{object.origin_country}
<br />
</>
)}
Country:
<b>Speed:</b>
{object.velocity} kn (
{Math.round(object.velocity * 1.852 * 100) / 100} km/h)
<br />
Speed:
</div>
<div style={{ fontWeight: "bold" }}>
{object.callsign}
<b>Position accurancy: </b>
{object.accurancy ? "high" : "low"}
<br />
{object.altitude && (
<br/>
{!vesselInfo ? (
<b>click on ship to get more info...</b>
) : (
<>
{object.altitude}m
<b>Name:</b>
<br />
{vesselInfo.name}
<br />
<b>Callsign:</b>
<br />
{vesselInfo.callSign}
<br />
<b>Destination:</b>
<br />
{vesselInfo.destination}
<br />
<b>Ship type:</b>
<br />
{vesselInfo.shipType}
</>
)}
{object.origin_country}
<br />
{object.velocity}mph
</div>
</div>
);
Expand Down
68 changes: 44 additions & 24 deletions src/components/MlIconLayer/MlIconLayer.stories.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useMemo, useEffect, useState, useContext } from "react";
import React, { useMemo, useEffect, useState, useContext, useRef } from "react";
import * as d3 from "d3";
import * as turf from "@turf/turf";

import MlIconLayer from "./MlIconLayer";

import mapContextDecorator from "../../decorators/MapContextKlokantechBasicDecorator";
import { MapContext, SimpleDataProvider } from "@mapcomponents/react-maplibre";
import { MapContext, MlWmsLayer, SimpleDataProvider } from "@mapcomponents/react-maplibre";

const storyoptions = {
title: "MapComponents/MlIconLayer",
Expand All @@ -21,16 +21,20 @@ export default storyoptions;
const Template = (args) => {
const mapContext = useContext(MapContext);
const [timeParam, setTimeParam] = useState();
const timeRef = useRef();


const dataUrl = useMemo(
// currently vv is used to prevent cache as time requires an opensky account
() =>
timeParam
? "https://api.opensky-network.org/api/states/all?vv=" + timeParam
? "https://meri.digitraffic.fi/api/ais/v1/locations?from=" + (timeParam)
: "",
[timeParam]
);

const plainDataUrl = "https://meri.digitraffic.fi/api/ais/v1/locations?mmsi=312691000";

const increaseTimeParam = () => {
setTimeParam(timeParam + 10);
};
Expand All @@ -41,43 +45,59 @@ const Template = (args) => {

useEffect(() => {
if (mapContext.map) {
mapContext.map.setZoom(8.5);
setTimeParam(Math.floor(new Date().getTime() / 1000) - 5);
//mapContext.map.setZoom(8.5);
mapContext.map.jumpTo({ center: [22.870581, 62.543826], zoom:5.5 });
setTimeParam(Math.floor(new Date().getTime()) - 5000);
}
}, [mapContext.map]);

return (
<>
<SimpleDataProvider
format="json"
url={dataUrl}
formatData={(d) => {
formatData={(d) => {
timeRef.current = new Date().getTime();
const props = d.properties;
return {
id:d[1],
callsign: d[1],
time_contact: (d[3]?d[3]:d[4]),
lon: d[5],
lat: d[6],
longitude: d[5],
latitude: d[6],
velocity: d[9],
altitude: d[13],
origin_country: d[2],
true_track: d[10],
mmsi: props.mmsi,
velocity:props.sog,
navStat: d.properties.navStat,
time_contact: props.timestampExternal,
longitude: d.geometry?.coordinates[0],
latitude: d.geometry?.coordinates[1],
true_track: props.cog,
accurancy: props.posAcc,
interpolatePos: d3.geoInterpolate(
[d[5], d[6]],
d[5] === null
? [d[5], d[6]]
: turf.transformTranslate(turf.point([d[5], d[6]]), d[9] * 10, d[10], {
units: "meters",
}).geometry.coordinates
[d.geometry?.coordinates[0], d.geometry?.coordinates[1]],
d.geometry?.coordinates[0] === null
? [d.geometry?.coordinates[0], d.geometry?.coordinates[1]]
:
turf.transformTranslate(
turf.point([
d.geometry?.coordinates[0],
d.geometry?.coordinates[1],
]),
props.sog * 5.14444444 , //distance in meters over 10 sec
props.heading,
{
units: "meters",
}
).geometry.coordinates
),
};
}}
data_property="states"
data_property="features"
onData={renewDataUrl}
>
<MlIconLayer />
</SimpleDataProvider>
<MlWmsLayer
url="https://openwms.statkart.no/skwms1/wms.dybdekurver_havomraader?"
layerId="dybdekontur_oversiktsdata"
urlParameters={{format: "image/png", layers: ["dybdekontur_oversiktsdata", "dybdekontur_label"], transparent: "true"}}
/>
</>
);
};

Expand Down
Binary file added src/components/MlIconLayer/assets/Ships.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/components/MlIconLayer/assets/Ships_v2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4057a2a

Please sign in to comment.