diff --git a/public/infantry-red.svg b/public/infantry-red.svg new file mode 100644 index 0000000000000000000000000000000000000000..c8b743b29b1c12afca0e305ec67923cad188e804 --- /dev/null +++ b/public/infantry-red.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="591.0625" height="372.0625" id="svg2"> + <defs id="defs4"> + <linearGradient id="linearGradient3777"> + <stop id="stop3779" style="stop-color:#b35800;stop-opacity:1" offset="0"/> + </linearGradient> + </defs> + <metadata id="metadata7"> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <g transform="translate(-72.28125,-210.75)" id="layer1"> + <path d="M 585.90976,366.37406 4.2663332,3.7357361" transform="translate(72.28125,210.75)" id="path3881" style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + <path d="M 584.7946,6.0174311 5.6411055,365.7946" transform="translate(72.28125,210.75)" id="path3883" style="fill:none;stroke:#000000;stroke-width:24;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + <rect width="583.06555" height="364.06042" x="76.266335" y="214.73877" id="rect3859" style="fill:none;stroke:#ff0000;stroke-width:24;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/> + </g> +</svg> \ No newline at end of file diff --git a/src/App.css b/src/App.css index b3004ac40734d1b05cd893b254948d77559da7dc..7a4add6f468dbdf25d90c5425991b36d765e2ea5 100644 --- a/src/App.css +++ b/src/App.css @@ -233,3 +233,45 @@ div.login button:hover { display: flex; flex-direction: column; } + +.leaflet-control-playback { + position: relative; + background-color: #7cbdf5; + padding: 10px; +} +.leaflet-control-playback .optionsContainer { + position: relative; +} +.leaflet-control-playback .optionsContainer > div { + display: inline-block; +} +.leaflet-control-playback .buttonContainer { +} +.leaflet-control-playback .buttonContainer a { + display: inline-block; + width: 32px; + height: 32px; + text-decoration: none; +} +.leaflet-control-playback .buttonContainer .btn-stop { + background: url(icons/icon-play.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-start { + background: url(icons/icon-stop.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-restart { + background: url(icons/icon-restart.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-slow { + background: url(icons/icon-slow.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-quick { + background: url(icons/icon-quick.png) no-repeat center; +} +.leaflet-control-playback .buttonContainer .btn-close { + background: url(icons/icon-close.png) no-repeat center; +} +.leaflet-control-playback .infoContainer { +} +.leaflet-control-playback .sliderContainer { +} diff --git a/src/App.js b/src/App.js index b701a6e8845cb04a430eb2d0baaa03976bc061ec..4a1bbae8a594404193575f3200b425dd48f8a69b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import "../node_modules/leaflet-draw/dist/leaflet.draw.css"; import "./App.css"; + import ClientSocket from "./components/Socket"; import { BrowserRouter as Router, @@ -12,6 +13,7 @@ import LoginForm from "./components/LoginForm"; import RegisterForm from "./components/RegisterForm"; import GameSelection from "./components/GameSelection"; import GameView from "./components/GameView"; +import ReplayMap from "./components/ReplayMap"; export default class App extends Component { constructor() { @@ -140,6 +142,11 @@ export default class App extends Component { /> ); }; + + replay = () => { + return <ReplayMap />; + }; + render() { // TODO: think better solution to wait for authenticator if (!this.state.authenticateComplete) { diff --git a/src/components/DrawGeoJSON.js b/src/components/DrawGeoJSON.js new file mode 100644 index 0000000000000000000000000000000000000000..fd7f4f3b464f6c3c4814217db17fe1056a3fcb25 --- /dev/null +++ b/src/components/DrawGeoJSON.js @@ -0,0 +1,134 @@ +import React from "react"; +import L from "leaflet"; +import "leaflet-draw"; +import { + Circle, + Marker, + Polygon, + Polyline, + Rectangle, + Tooltip +} from "react-leaflet"; + +// an empty icon for textboxes +let noIcon = L.divIcon({ + className: "", + iconSize: [20, 20], + iconAnchor: [10, 20] +}); + +class DrawGeoJSON extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( + <React.Fragment> + {/* iterate through every element fetched from back-end */} + {this.props.geoJSONLayer.features.map(feature => { + let id = feature.mapDrawingHistoryId; + let coords = feature.data.geometry.coordinates; + let type = feature.data.geometry.type; + let color = feature.data.properties.color; + let radius = feature.data.properties.radius; + let text = feature.data.properties.text; + let rectangle = feature.data.properties.rectangle; + if (type === "Point") { + // GeoJSON saves latitude first, not longitude like usual. swapping + let position = [coords[1], coords[0]]; + if (radius) { + return ( + // keys are required to be able to edit + <Circle + key={Math.random()} + center={position} + id={id} + radius={radius} + color={color} + /> + ); + } else if (text) { + return ( + <Marker + key={Math.random()} + position={position} + id={id} + color={color} + icon={noIcon} + > + <Tooltip + direction="bottom" + permanent + className="editable" + interactive={true} + > + <div class="editable"> + <div + contenteditable="true" + placeholder="Click out to save" + > + {text} + </div> + </div> + </Tooltip> + </Marker> + ); + } else { + // unknown if color changes anything. need to test + return ( + <Marker + key={Math.random()} + position={position} + id={id} + color={color} + /> + ); + } + } else if (rectangle) { + // instead of an array of four coordinates, rectangles only have two corners + let bounds = coords[0].map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Rectangle + key={Math.random()} + bounds={bounds} + id={id} + color={color} + /> + ); + } else if (type === "Polygon") { + // Polygon coordinates are wrapped under a one element array, for some reason + let positions = coords[0].map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Polygon + key={Math.random()} + positions={positions} + id={id} + color={color} + /> + ); + } else if (type === "LineString") { + // Polyline coordinates are a normal array, unlike Polygon + let positions = coords.map(coord => { + return [coord[1], coord[0]]; + }); + return ( + <Polyline + key={Math.random()} + positions={positions} + id={id} + color={color} + /> + ); + } + })} + </React.Fragment> + ); + } +} + +export default DrawGeoJSON; diff --git a/src/components/ReplayMap.js b/src/components/ReplayMap.js new file mode 100644 index 0000000000000000000000000000000000000000..c1396c0ac5b9a20cbbcaddde36ac1f4bcb84c033 --- /dev/null +++ b/src/components/ReplayMap.js @@ -0,0 +1,170 @@ +// https://github.com/linghuam/Leaflet.TrackPlayBack + +import React from "react"; +import L from "leaflet"; +import { Map, TileLayer, ZoomControl, Marker, Popup } from "react-leaflet"; +import "../track-playback/src/leaflet.trackplayback/clock"; +import "../track-playback/src/leaflet.trackplayback/index"; +import "../track-playback/src/control.trackplayback/control.playback"; +import "../track-playback/src/control.trackplayback/index"; +import DrawGeoJSON from "./DrawGeoJSON"; + +export default class ReplayMap extends React.Component { + constructor(props) { + super(props); + this.state = { + // stores the playback object + playback: null, + // stores all drawings from backend + allGeoJSON: [], + // stores all active drawings on the map + activeGeoJSON: [] + }; + this.map = React.createRef(); + } + + async componentDidMount() { + await this.fetchPlayerData(); + await this.fetchDrawingData(); + this.tickDrawings(); + this.replay(); + } + + componentWillReceiveProps(nextProps) {} + + // cloud game a1231e2b-aa29-494d-b687-ea2d48cc23df + // fetch player locations from the game + fetchPlayerData = async () => { + await fetch( + `${ + process.env.REACT_APP_API_URL + }/replay/players/a1231e2b-aa29-494d-b687-ea2d48cc23df`, + { + method: "GET" + } + ) + .then(async res => await res.json()) + .then( + async res => { + await this.setState({ data: res }); + }, + // Note: it's important to handle errors here + // instead of a catch() block so that we don't swallow + // exceptions from actual bugs in components. + error => { + console.log(error); + } + ); + }; + + fetchDrawingData = async () => { + await fetch( + /* `${ + process.env.REACT_APP_API_URL + }/replay/a1231e2b-aa29-494d-b687-ea2d48cc23df`, */ + `http://localhost:5000/replay/15e9563b-e621-4ba1-a440-1b21c7774923`, + { + method: "GET" + } + ) + .then(async res => await res.json()) + .then( + async res => { + await this.setState({ allGeoJSON: res }); + }, + error => { + console.log(error); + } + ); + }; + + tickDrawings = () => { + let activeDrawings = []; + this.state.allGeoJSON.map(drawing => { + activeDrawings.push(drawing[0]); + this.setState({ + activeGeoJSON: { + type: "FeatureCollection", + features: [...activeDrawings] + } + }); + }); + }; + + replay = () => { + this.map = L.map(this.refs.map).setView([62.3, 25.7], 15); + L.tileLayer("https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg", { + attribution: + '© <a href="https://www.maanmittauslaitos.fi/">Maanmittauslaitos</a>' + }).addTo(this.map); + this.trackplayback = new L.TrackPlayBack(this.state.data, this.map, { + trackPointOptions: { + // whether to draw track point + isDraw: false, + // whether to use canvas to draw it, if false, use leaflet api `L.circleMarker` + useCanvas: true, + stroke: true, + color: "#ef0300", + fill: true, + fillColor: "#ef0300", + opacity: 0.3, + radius: 4 + }, + targetOptions: { + // whether to use an image to display target, if false, the program provides a default + useImg: true, + // if useImg is true, provide the imgUrl + imgUrl: "../infantry-red.svg", + // the width of target, unit: px + width: 60, + // the height of target, unit: px + height: 40, + // the stroke color of target, effective when useImg set false + color: "#00f", + // the fill color of target, effective when useImg set false + fillColor: "#9FD12D" + }, + clockOptions: { + // the default speed + // caculate method: fpstime * Math.pow(2, speed - 1) + // fpstime is the two frame time difference + speed: 10, + // the max speed + maxSpeed: 100 + } + }); + this.setState({ + playback: this.trackplayback + }); + this.trackplaybackControl = L.trackplaybackcontrol(this.trackplayback); + this.trackplaybackControl.addTo(this.map); + }; + + render() { + return ( + /* <Map + className="map" + ref={this.map} + center={[62.3, 25.7]} + zoom={15} + minZoom="7" + maxZoom="17" + zoomControl={false} + > + <TileLayer + attribution='© <a href="https://www.maanmittauslaitos.fi/">Maanmittauslaitos</a>' + url={"https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg"} + /> + <ZoomControl position="topright" /> + {this.state.activeGeoJSON.features && ( + <DrawGeoJSON geoJSONLayer={this.state.activeGeoJSON} /> + )} + </Map> */ + <div className="map" ref="map"> + {/* {this.state.activeGeoJSON.features && ( + <DrawGeoJSON geoJSONLayer={this.state.activeGeoJSON} /> + )} */} + </div> + ); + } +} diff --git a/src/icons/icon-close.png b/src/icons/icon-close.png new file mode 100644 index 0000000000000000000000000000000000000000..81bef1ad48176c82033dcb527940cff736a53818 Binary files /dev/null and b/src/icons/icon-close.png differ diff --git a/src/icons/icon-play.png b/src/icons/icon-play.png new file mode 100644 index 0000000000000000000000000000000000000000..3af78948ec238cc2fc13e5a2e16569c516b1cd22 Binary files /dev/null and b/src/icons/icon-play.png differ diff --git a/src/icons/icon-quick.png b/src/icons/icon-quick.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc45fd114d7f5813be62cebde2bd95ccedcdceb Binary files /dev/null and b/src/icons/icon-quick.png differ diff --git a/src/icons/icon-restart.png b/src/icons/icon-restart.png new file mode 100644 index 0000000000000000000000000000000000000000..022c4e60fdec44ee1a2d605c85b1f156c16c7cb6 Binary files /dev/null and b/src/icons/icon-restart.png differ diff --git a/src/icons/icon-slow.png b/src/icons/icon-slow.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa1fbdaf382287502240e0987e1df0b8840ee8e Binary files /dev/null and b/src/icons/icon-slow.png differ diff --git a/src/icons/icon-stop.png b/src/icons/icon-stop.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee3137966264afc7119579bf52a7a1bb457e753 Binary files /dev/null and b/src/icons/icon-stop.png differ diff --git a/src/track-playback/src/control.trackplayback/control.playback.js b/src/track-playback/src/control.trackplayback/control.playback.js new file mode 100644 index 0000000000000000000000000000000000000000..e2ad4727e7e800222e41edf42af5602e2fb3f2d8 --- /dev/null +++ b/src/track-playback/src/control.trackplayback/control.playback.js @@ -0,0 +1,334 @@ +import L from "leaflet"; + +export const TrackPlayBackControl = L.Control.extend({ + options: { + position: "topright", + showOptions: true, + showInfo: true, + showSlider: true, + autoPlay: false + }, + + initialize: function(trackplayback, options) { + L.Control.prototype.initialize.call(this, options); + this.trackPlayBack = trackplayback; + this.trackPlayBack.on("tick", this._tickCallback, this); + }, + + onAdd: function(map) { + this._initContainer(); + return this._container; + }, + + onRemove: function(map) { + this.trackPlayBack.dispose(); + this.trackPlayBack.off("tick", this._tickCallback, this); + }, + + /** + * æ ¹æ®unix时间戳(å•ä½:秒)获å–时间å—符串 + * @param {[int]} time [时间戳(精确到秒)] + * @param {[string]} accuracy [精度,日:d, å°æ—¶ï¼šh,分钟:m,秒:s] + * @return {[string]} [yy:mm:dd hh:mm:ss] + */ + getTimeStrFromUnix: function(time, accuracy = "s") { + return ` + ${new Date(time).toLocaleDateString("en-US")} + ${new Date(time * 1e3).toISOString().slice(-13, -5)} + `; + /* time = parseInt(time * 1000); + let newDate = new Date(time); + let year = newDate.getFullYear(); + let month = + newDate.getMonth() + 1 < 10 + ? "0" + (newDate.getMonth() + 1) + : newDate.getMonth() + 1; + let day = + newDate.getDate() < 10 ? "0" + newDate.getDate() : newDate.getDate(); + let hours = + newDate.getHours() < 10 ? "0" + newDate.getHours() : newDate.getHours(); + let minuts = + newDate.getMinutes() < 10 + ? "0" + newDate.getMinutes() + : newDate.getMinutes(); + let seconds = + newDate.getSeconds() < 10 + ? "0" + newDate.getSeconds() + : newDate.getSeconds(); + let ret; + if (accuracy === "d") { + ret = year + "-" + month + "-" + day; + } else if (accuracy === "h") { + ret = year + "-" + month + "-" + day + " " + hours; + } else if (accuracy === "m") { + ret = year + "-" + month + "-" + day + " " + hours + ":" + minuts; + } else { + ret = + year + + "-" + + month + + "-" + + day + + " " + + hours + + ":" + + minuts + + ":" + + seconds; + } + return ret; */ + }, + + _initContainer: function() { + var className = "leaflet-control-playback"; + this._container = L.DomUtil.create("div", className); + L.DomEvent.disableClickPropagation(this._container); + + this._optionsContainer = this._createContainer( + "optionsContainer", + this._container + ); + this._buttonContainer = this._createContainer( + "buttonContainer", + this._container + ); + this._infoContainer = this._createContainer( + "infoContainer", + this._container + ); + this._sliderContainer = this._createContainer( + "sliderContainer", + this._container + ); + + this._pointCbx = this._createCheckbox( + "show trackPoint", + "show-trackpoint", + this._optionsContainer, + this._showTrackPoint + ); + this._lineCbx = this._createCheckbox( + "show trackLine", + "show-trackLine", + this._optionsContainer, + this._showTrackLine + ); + + this._playBtn = this._createButton( + "play", + "btn-stop", + this._buttonContainer, + this._play + ); + this._restartBtn = this._createButton( + "replay", + "btn-restart", + this._buttonContainer, + this._restart + ); + this._slowSpeedBtn = this._createButton( + "slow", + "btn-slow", + this._buttonContainer, + this._slow + ); + this._quickSpeedBtn = this._createButton( + "quick", + "btn-quick", + this._buttonContainer, + this._quick + ); + /* this._closeBtn = this._createButton( + "close", + "btn-close", + this._buttonContainer, + this._close + ); */ + + this._infoStartTime = this._createInfo( + "Game started: ", + this.getTimeStrFromUnix(this.trackPlayBack.getStartTime()), + "info-start-time", + this._infoContainer + ); + this._infoEndTime = this._createInfo( + "Game ended: ", + this.getTimeStrFromUnix(this.trackPlayBack.getEndTime()), + "info-end-time", + this._infoContainer + ); + this._infoCurTime = this._createInfo( + "Current time: ", + this.getTimeStrFromUnix(this.trackPlayBack.getCurTime()), + "info-cur-time", + this._infoContainer + ); + this._infoSpeedRatio = this._createInfo( + "speed: ", + `X${this.trackPlayBack.getSpeed()}`, + "info-speed-ratio", + this._infoContainer + ); + + this._slider = this._createSlider( + "time-slider", + this._sliderContainer, + this._scrollchange + ); + + return this._container; + }, + + _createContainer: function(className, container) { + return L.DomUtil.create("div", className, container); + }, + + _createCheckbox: function(labelName, className, container, fn) { + let divEle = L.DomUtil.create( + "div", + className + " trackplayback-checkbox", + container + ); + + let inputEle = L.DomUtil.create("input", "trackplayback-input", divEle); + let inputId = `trackplayback-input-${L.Util.stamp(inputEle)}`; + inputEle.setAttribute("type", "checkbox"); + inputEle.setAttribute("id", inputId); + + let labelEle = L.DomUtil.create("label", "trackplayback-label", divEle); + labelEle.setAttribute("for", inputId); + labelEle.innerHTML = labelName; + + L.DomEvent.on(inputEle, "change", fn, this); + + return divEle; + }, + + _createButton: function(title, className, container, fn) { + let link = L.DomUtil.create("a", className, container); + link.href = "#"; + link.title = title; + + /* + * Will force screen readers like VoiceOver to read this as "Zoom in - button" + */ + link.setAttribute("role", "button"); + link.setAttribute("aria-label", title); + + L.DomEvent.disableClickPropagation(link); + L.DomEvent.on(link, "click", fn, this); + + return link; + }, + + _createInfo: function(title, info, className, container) { + let infoContainer = L.DomUtil.create("div", "info-container", container); + let infoTitle = L.DomUtil.create("span", "info-title", infoContainer); + infoTitle.innerHTML = title; + let infoEle = L.DomUtil.create("span", className, infoContainer); + infoEle.innerHTML = info; + return infoEle; + }, + + _createSlider: function(className, container, fn) { + let sliderEle = L.DomUtil.create("input", className, container); + sliderEle.setAttribute("type", "range"); + sliderEle.setAttribute("min", this.trackPlayBack.getStartTime()); + sliderEle.setAttribute("max", this.trackPlayBack.getEndTime()); + sliderEle.setAttribute("value", this.trackPlayBack.getCurTime()); + + L.DomEvent.on( + sliderEle, + "click mousedown dbclick", + L.DomEvent.stopPropagation + ) + .on(sliderEle, "click", L.DomEvent.preventDefault) + .on(sliderEle, "change", fn, this) + .on(sliderEle, "mousemove", fn, this); + + return sliderEle; + }, + + _showTrackPoint(e) { + if (e.target.checked) { + this.trackPlayBack.showTrackPoint(); + } else { + this.trackPlayBack.hideTrackPoint(); + } + }, + + _showTrackLine(e) { + if (e.target.checked) { + this.trackPlayBack.showTrackLine(); + } else { + this.trackPlayBack.hideTrackLine(); + } + }, + + _play: function() { + let hasClass = L.DomUtil.hasClass(this._playBtn, "btn-stop"); + if (hasClass) { + L.DomUtil.removeClass(this._playBtn, "btn-stop"); + L.DomUtil.addClass(this._playBtn, "btn-start"); + this._playBtn.setAttribute("title", "stop"); + this.trackPlayBack.start(); + } else { + L.DomUtil.removeClass(this._playBtn, "btn-start"); + L.DomUtil.addClass(this._playBtn, "btn-stop"); + this._playBtn.setAttribute("title", "play"); + this.trackPlayBack.stop(); + } + }, + + _restart: function() { + // æ’放开始改å˜æ’æ”¾æŒ‰é’®æ ·å¼ + L.DomUtil.removeClass(this._playBtn, "btn-stop"); + L.DomUtil.addClass(this._playBtn, "btn-start"); + this._playBtn.setAttribute("title", "stop"); + this.trackPlayBack.rePlaying(); + }, + + _slow: function() { + this.trackPlayBack.slowSpeed(); + let sp = this.trackPlayBack.getSpeed(); + this._infoSpeedRatio.innerHTML = `X${sp}`; + }, + + _quick: function() { + this.trackPlayBack.quickSpeed(); + let sp = this.trackPlayBack.getSpeed(); + this._infoSpeedRatio.innerHTML = `X${sp}`; + }, + + _close: function() { + L.DomUtil.remove(this._container); + if (this.onRemove) { + this.onRemove(this._map); + } + return this; + }, + + _scrollchange: function(e) { + let val = Number(e.target.value); + this.trackPlayBack.setCursor(val); + }, + + _tickCallback: function(e) { + // 更新时间 + let time = this.getTimeStrFromUnix(e.time); + this._infoCurTime.innerHTML = time; + // 更新时间轴 + this._slider.value = e.time; + // æ’放结æŸåŽæ”¹å˜æ’æ”¾æŒ‰é’®æ ·å¼ + if (e.time >= this.trackPlayBack.getEndTime()) { + L.DomUtil.removeClass(this._playBtn, "btn-start"); + L.DomUtil.addClass(this._playBtn, "btn-stop"); + this._playBtn.setAttribute("title", "play"); + this.trackPlayBack.stop(); + } + } +}); + +export const trackplaybackcontrol = function(trackplayback, options) { + return new TrackPlayBackControl(trackplayback, options); +}; diff --git a/src/track-playback/src/control.trackplayback/index.js b/src/track-playback/src/control.trackplayback/index.js new file mode 100644 index 0000000000000000000000000000000000000000..599a2df5932224ccd4af7d3146dfa8d078d9c6ab --- /dev/null +++ b/src/track-playback/src/control.trackplayback/index.js @@ -0,0 +1,5 @@ +import L from "leaflet"; +import { TrackPlayBackControl, trackplaybackcontrol } from "./control.playback"; + +L.TrackPlayBackControl = TrackPlayBackControl; +L.trackplaybackcontrol = trackplaybackcontrol; diff --git a/src/track-playback/src/leaflet.trackplayback/clock.js b/src/track-playback/src/leaflet.trackplayback/clock.js new file mode 100644 index 0000000000000000000000000000000000000000..83ff85bdcfeb6f7e85707282e250052deb37c622 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/clock.js @@ -0,0 +1,132 @@ +import L from "leaflet"; +/** + * 时钟类,控制轨迹æ’放动画 + */ +export const Clock = L.Class.extend({ + includes: L.Mixin.Events, + + options: { + // æ’放速度 + // 计算方法 fpstime * Math.pow(2, this._speed - 1) + speed: 12, + // 最大æ’放速度 + maxSpeed: 65 + }, + + initialize: function(trackController, options) { + L.setOptions(this, options); + + this._trackController = trackController; + this._endTime = this._trackController.getMaxTime(); + this._curTime = this._trackController.getMinTime(); + this._speed = this.options.speed; + this._maxSpeed = this.options.maxSpeed; + this._intervalID = null; + this._lastFpsUpdateTime = 0; + }, + + start: function() { + if (this._intervalID) return; + this._intervalID = L.Util.requestAnimFrame(this._tick, this); + }, + + stop: function() { + if (!this._intervalID) return; + L.Util.cancelAnimFrame(this._intervalID); + this._intervalID = null; + this._lastFpsUpdateTime = 0; + }, + + rePlaying: function() { + this.stop(); + this._curTime = this._trackController.getMinTime(); + this.start(); + }, + + slowSpeed: function() { + this._speed = this._speed <= 1 ? this._speed : this._speed - 1; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + quickSpeed: function() { + this._speed = this._speed >= this._maxSpeed ? this._speed : this._speed + 1; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + getSpeed: function() { + return this._speed; + }, + + getCurTime: function() { + return this._curTime; + }, + + getStartTime: function() { + return this._trackController.getMinTime(); + }, + + getEndTime: function() { + return this._trackController.getMaxTime(); + }, + + isPlaying: function() { + return !!this._intervalID; + }, + + setCursor: function(time) { + this._curTime = time; + this._trackController.drawTracksByTime(this._curTime); + this.fire("tick", { + time: this._curTime + }); + }, + + setSpeed: function(speed) { + this._speed = speed; + if (this._intervalID) { + this.stop(); + this.start(); + } + }, + + // 计算两帧时间间隔,å•ä½ï¼šç§’ + _caculatefpsTime: function(now) { + let time; + if (this._lastFpsUpdateTime === 0) { + time = 0; + } else { + time = now - this._lastFpsUpdateTime; + } + this._lastFpsUpdateTime = now; + // 将毫秒转æ¢æˆç§’ + time = time / 1000; + return time; + }, + + _tick: function() { + let now = +new Date(); + let fpstime = this._caculatefpsTime(now); + let isPause = false; + let stepTime = fpstime * Math.pow(2, this._speed - 1); + this._curTime += stepTime; + if (this._curTime >= this._endTime) { + this._curTime = this._endTime; + isPause = true; + } + this._trackController.drawTracksByTime(this._curTime); + this.fire("tick", { + time: this._curTime + }); + if (!isPause) this._intervalID = L.Util.requestAnimFrame(this._tick, this); + } +}); + +export const clock = function(trackController, options) { + return new Clock(trackController, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/draw.js b/src/track-playback/src/leaflet.trackplayback/draw.js new file mode 100644 index 0000000000000000000000000000000000000000..7100dc7f3eb6242a922d112016528b14071caabf --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/draw.js @@ -0,0 +1,353 @@ +import L from "leaflet"; + +import { TrackLayer } from "./tracklayer"; + +/** + * 绘制类 + * 完æˆè½¨è¿¹çº¿ã€è½¨è¿¹ç‚¹ã€ç›®æ ‡ç‰©çš„绘制工作 + */ +export const Draw = L.Class.extend({ + trackPointOptions: { + isDraw: false, + useCanvas: true, + stroke: false, + color: "#ef0300", + fill: true, + fillColor: "#ef0300", + opacity: 0.3, + radius: 4 + }, + trackLineOptions: { + isDraw: false, + stroke: true, + color: "#1C54E2", // stroke color + weight: 2, + fill: false, + fillColor: "#000", + opacity: 0.3 + }, + targetOptions: { + useImg: false, + imgUrl: "../../static/images/ship.png", + showText: false, + width: 8, + height: 18, + color: "#00f", // stroke color + fillColor: "#9FD12D" + }, + toolTipOptions: { + offset: [0, 0], + direction: "top", + permanent: false + }, + + initialize: function(map, options) { + L.extend(this.trackPointOptions, options.trackPointOptions); + L.extend(this.trackLineOptions, options.trackLineOptions); + L.extend(this.targetOptions, options.targetOptions); + L.extend(this.toolTipOptions, options.toolTipOptions); + + this._showTrackPoint = this.trackPointOptions.isDraw; + this._showTrackLine = this.trackLineOptions.isDraw; + + this._map = map; + this._map.on("mousemove", this._onmousemoveEvt, this); + + this._trackLayer = new TrackLayer().addTo(map); + this._trackLayer.on("update", this._trackLayerUpdate, this); + + this._canvas = this._trackLayer.getContainer(); + this._ctx = this._canvas.getContext("2d"); + + this._bufferTracks = []; + + if (!this.trackPointOptions.useCanvas) { + this._trackPointFeatureGroup = L.featureGroup([]).addTo(map); + } + + // ç›®æ ‡å¦‚æžœä½¿ç”¨å›¾ç‰‡ï¼Œå…ˆåŠ è½½å›¾ç‰‡ + if (this.targetOptions.useImg) { + const img = new Image(); + img.onload = () => { + this._targetImg = img; + }; + img.onerror = () => { + throw new Error("img load error!"); + }; + img.src = this.targetOptions.imgUrl; + } + }, + + update: function() { + this._trackLayerUpdate(); + }, + + drawTrack: function(trackpoints) { + this._bufferTracks.push(trackpoints); + this._drawTrack(trackpoints); + }, + + showTrackPoint: function() { + this._showTrackPoint = true; + this.update(); + }, + + hideTrackPoint: function() { + this._showTrackPoint = false; + this.update(); + }, + + showTrackLine: function() { + this._showTrackLine = true; + this.update(); + }, + + hideTrackLine: function() { + this._showTrackLine = false; + this.update(); + }, + + remove: function() { + this._bufferTracks = []; + this._trackLayer.off("update", this._trackLayerUpdate, this); + this._map.off("mousemove", this._onmousemoveEvt, this); + if (this._map.hasLayer(this._trackLayer)) { + this._map.removeLayer(this._trackLayer); + } + if (this._map.hasLayer(this._trackPointFeatureGroup)) { + this._map.removeLayer(this._trackPointFeatureGroup); + } + }, + + clear: function() { + this._clearLayer(); + this._bufferTracks = []; + }, + + _trackLayerUpdate: function() { + if (this._bufferTracks.length) { + this._clearLayer(); + this._bufferTracks.forEach( + function(element, index) { + this._drawTrack(element); + }.bind(this) + ); + } + }, + + _onmousemoveEvt: function(e) { + if (!this._showTrackPoint) { + return; + } + let point = e.layerPoint; + if (this._bufferTracks.length) { + for (let i = 0, leni = this._bufferTracks.length; i < leni; i++) { + for (let j = 0, len = this._bufferTracks[i].length; j < len; j++) { + let tpoint = this._getLayerPoint(this._bufferTracks[i][j]); + if (point.distanceTo(tpoint) <= this.trackPointOptions.radius) { + this._opentoolTip(this._bufferTracks[i][j]); + return; + } + } + } + } + if (this._map.hasLayer(this._tooltip)) { + this._map.removeLayer(this._tooltip); + } + this._canvas.style.cursor = "pointer"; + }, + + _opentoolTip: function(trackpoint) { + if (this._map.hasLayer(this._tooltip)) { + this._map.removeLayer(this._tooltip); + } + this._canvas.style.cursor = "default"; + let latlng = L.latLng(trackpoint.lat, trackpoint.lng); + let tooltip = (this._tooltip = L.tooltip(this.toolTipOptions)); + tooltip.setLatLng(latlng); + tooltip.addTo(this._map); + tooltip.setContent(this._getTooltipText(trackpoint)); + }, + + _drawTrack: function(trackpoints) { + // 画轨迹线 + if (this._showTrackLine) { + this._drawTrackLine(trackpoints); + } + // 画船 + let targetPoint = trackpoints[trackpoints.length - 1]; + if (this.targetOptions.useImg && this._targetImg) { + this._drawShipImage(targetPoint); + } else { + this._drawShipCanvas(targetPoint); + } + // ç”»æ ‡æ³¨ä¿¡æ¯ + if (this.targetOptions.showText) { + this._drawtxt(`航å‘:${parseInt(targetPoint.dir)}度`, targetPoint); + } + // ç”»ç»è¿‡çš„轨迹点 + if (this._showTrackPoint) { + if (this.trackPointOptions.useCanvas) { + this._drawTrackPointsCanvas(trackpoints); + } else { + this._drawTrackPointsSvg(trackpoints); + } + } + }, + + _drawTrackLine: function(trackpoints) { + let options = this.trackLineOptions; + let tp0 = this._getLayerPoint(trackpoints[0]); + this._ctx.save(); + this._ctx.beginPath(); + // 画轨迹线 + this._ctx.moveTo(tp0.x, tp0.y); + for (let i = 1, len = trackpoints.length; i < len; i++) { + let tpi = this._getLayerPoint(trackpoints[i]); + this._ctx.lineTo(tpi.x, tpi.y); + } + this._ctx.globalAlpha = options.opacity; + if (options.stroke) { + this._ctx.strokeStyle = options.color; + this._ctx.lineWidth = options.weight; + this._ctx.stroke(); + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor; + this._ctx.fill(); + } + this._ctx.restore(); + }, + + _drawTrackPointsCanvas: function(trackpoints) { + let options = this.trackPointOptions; + this._ctx.save(); + for (let i = 0, len = trackpoints.length; i < len; i++) { + if (trackpoints[i].isOrigin) { + let latLng = L.latLng(trackpoints[i].lat, trackpoints[i].lng); + let radius = options.radius; + let point = this._map.latLngToLayerPoint(latLng); + this._ctx.beginPath(); + this._ctx.arc(point.x, point.y, radius, 0, Math.PI * 2, false); + this._ctx.globalAlpha = options.opacity; + if (options.stroke) { + this._ctx.strokeStyle = options.color; + this._ctx.stroke(); + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor; + this._ctx.fill(); + } + } + } + this._ctx.restore(); + }, + + _drawTrackPointsSvg: function(trackpoints) { + for (let i = 0, len = trackpoints.length; i < len; i++) { + if (trackpoints[i].isOrigin) { + let latLng = L.latLng(trackpoints[i].lat, trackpoints[i].lng); + let cricleMarker = L.circleMarker(latLng, this.trackPointOptions); + cricleMarker.bindTooltip( + this._getTooltipText(trackpoints[i]), + this.toolTipOptions + ); + this._trackPointFeatureGroup.addLayer(cricleMarker); + } + } + }, + + _drawtxt: function(text, trackpoint) { + let point = this._getLayerPoint(trackpoint); + this._ctx.save(); + this._ctx.font = "12px Verdana"; + this._ctx.fillStyle = "#000"; + this._ctx.textAlign = "center"; + this._ctx.textBaseline = "bottom"; + this._ctx.fillText(text, point.x, point.y - 12, 200); + this._ctx.restore(); + }, + + _drawShipCanvas: function(trackpoint) { + let point = this._getLayerPoint(trackpoint); + let rotate = trackpoint.dir || 0; + let w = this.targetOptions.width; + let h = this.targetOptions.height; + let dh = h / 3; + + this._ctx.save(); + this._ctx.fillStyle = this.targetOptions.fillColor; + this._ctx.strokeStyle = this.targetOptions.color; + this._ctx.translate(point.x, point.y); + this._ctx.rotate((Math.PI / 180) * rotate); + this._ctx.beginPath(); + this._ctx.moveTo(0, 0 - h / 2); + this._ctx.lineTo(0 - w / 2, 0 - h / 2 + dh); + this._ctx.lineTo(0 - w / 2, 0 + h / 2); + this._ctx.lineTo(0 + w / 2, 0 + h / 2); + this._ctx.lineTo(0 + w / 2, 0 - h / 2 + dh); + this._ctx.closePath(); + this._ctx.fill(); + this._ctx.stroke(); + this._ctx.restore(); + }, + + _drawShipImage: function(trackpoint) { + let point = this._getLayerPoint(trackpoint); + let width = this.targetOptions.width; + let height = this.targetOptions.height; + let offset = { + x: width / 2, + y: height / 2 + }; + this._ctx.save(); + this._ctx.translate(point.x, point.y); + this._ctx.drawImage( + this._targetImg, + 0 - offset.x, + 0 - offset.y, + width, + height + ); + this._ctx.restore(); + }, + + _getTooltipText: function(targetobj) { + let content = []; + content.push("<table>"); + if (targetobj.info && targetobj.info.length) { + for (let i = 0, len = targetobj.info.length; i < len; i++) { + content.push("<tr>"); + content.push("<td>" + targetobj.info[i].key + "</td>"); + content.push("<td>" + targetobj.info[i].value + "</td>"); + content.push("</tr>"); + } + } + content.push("</table>"); + content = content.join(""); + return content; + }, + + _clearLayer: function() { + let bounds = this._trackLayer.getBounds(); + if (bounds) { + let size = bounds.getSize(); + this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); + } else { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + if (this._map.hasLayer(this._trackPointFeatureGroup)) { + this._trackPointFeatureGroup.clearLayers(); + } + }, + + _getLayerPoint(trackpoint) { + return this._map.latLngToLayerPoint( + L.latLng(trackpoint.lat, trackpoint.lng) + ); + } +}); + +export const draw = function(map, options) { + return new Draw(map, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/index.js b/src/track-playback/src/leaflet.trackplayback/index.js new file mode 100644 index 0000000000000000000000000000000000000000..defba9838814140a2047a331ff2825ddd0e22e1e --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/index.js @@ -0,0 +1,9 @@ +import L from 'leaflet' + +import { + TrackPlayBack, + trackplayback +} from './trackplayback' + +L.TrackPlayBack = TrackPlayBack +L.trackplayback = trackplayback diff --git a/src/track-playback/src/leaflet.trackplayback/track.js b/src/track-playback/src/leaflet.trackplayback/track.js new file mode 100644 index 0000000000000000000000000000000000000000..c770c3f3e61a3b70b964be786f9cd77dcd39467f --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/track.js @@ -0,0 +1,166 @@ +import L from "leaflet"; + +import { isArray } from "./util"; + +/** + * 轨迹类 + */ +export const Track = L.Class.extend({ + initialize: function(trackData = [], options) { + L.setOptions(this, options); + + trackData.forEach(item => { + // æ·»åŠ isOrigin å—段用æ¥æ ‡è¯†æ˜¯å¦æ˜¯åŽŸå§‹é‡‡æ ·ç‚¹ï¼Œä¸Žæ’值点区分开 + item.isOrigin = true; + }); + this._trackPoints = trackData; + this._timeTick = {}; + this._update(); + }, + + addTrackPoint: function(trackPoint) { + if (isArray(trackPoint)) { + for (let i = 0, len = trackPoint.length; i < len; i++) { + this.addTrackPoint(trackPoint[i]); + } + } + this._addTrackPoint(trackPoint); + }, + + getTimes: function() { + let times = []; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + times.push(this._trackPoints[i].time); + } + return times; + }, + + getStartTrackPoint: function() { + return this._trackPoints[0]; + }, + + getEndTrackPoint: function() { + return this._trackPoints[this._trackPoints.length - 1]; + }, + + getTrackPointByTime: function(time) { + return this._trackPoints[this._timeTick[time]]; + }, + + _getCalculateTrackPointByTime: function(time) { + // 先判æ–最åŽä¸€ä¸ªç‚¹æ˜¯å¦ä¸ºåŽŸå§‹ç‚¹ + let endpoint = this.getTrackPointByTime(time); + let startPt = this.getStartTrackPoint(); + let endPt = this.getEndTrackPoint(); + let times = this.getTimes(); + if (time < startPt.time || time > endPt.time) return; + let left = 0; + let right = times.length - 1; + let n; + // 处ç†åªæœ‰ä¸€ä¸ªç‚¹æƒ…况 + if (left === right) { + return endpoint; + } + // 通过ã€äºŒåˆ†æŸ¥æ‰¾ã€‘法查出当å‰æ—¶é—´æ‰€åœ¨çš„时间区间 + while (right - left !== 1) { + n = parseInt((left + right) / 2); + if (time > times[n]) left = n; + else right = n; + } + + let t0 = times[left]; + let t1 = times[right]; + let t = time; + let p0 = this.getTrackPointByTime(t0); + let p1 = this.getTrackPointByTime(t1); + startPt = L.point(p0.lng, p0.lat); + endPt = L.point(p1.lng, p1.lat); + let s = startPt.distanceTo(endPt); + // ä¸åŒæ—¶é—´åœ¨åŒä¸€ä¸ªç‚¹æƒ…å½¢ + if (s <= 0) { + endpoint = p1; + return endpoint; + } + // å‡è®¾ç›®æ ‡åœ¨ä¸¤ç‚¹é—´åšåŒ€é€Ÿç›´çº¿è¿åŠ¨ + // 求解速度å‘é‡ï¼Œå¹¶è®¡ç®—时间 t ç›®æ ‡æ‰€åœ¨ä½ç½® + let v = s / (t1 - t0); + let sinx = (endPt.y - startPt.y) / s; + let cosx = (endPt.x - startPt.x) / s; + let step = v * (t - t0); + let x = startPt.x + step * cosx; + let y = startPt.y + step * sinx; + // æ±‚ç›®æ ‡çš„è¿åŠ¨æ–¹å‘,0-360度 + let dir = + endPt.x >= startPt.x + ? ((Math.PI * 0.5 - Math.asin(sinx)) * 180) / Math.PI + : ((Math.PI * 1.5 + Math.asin(sinx)) * 180) / Math.PI; + + if (endpoint) { + if (endpoint.dir === undefined) { + endpoint.dir = dir; + } + } else { + endpoint = { + lng: x, + lat: y, + dir: endPt.dir || dir, + isOrigin: false, + time: time + }; + } + return endpoint; + }, + + // 获å–æŸä¸ªæ—¶é—´ç‚¹ä¹‹å‰èµ°è¿‡çš„轨迹 + getTrackPointsBeforeTime: function(time) { + let tpoints = []; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + if (this._trackPoints[i].time < time) { + tpoints.push(this._trackPoints[i]); + } + } + // 获å–最åŽä¸€ä¸ªç‚¹ï¼Œæ ¹æ®æ—¶é—´çº¿æ€§æ’å€¼è€Œæ¥ + let endPt = this._getCalculateTrackPointByTime(time); + if (endPt) { + tpoints.push(endPt); + } + return tpoints; + }, + + _addTrackPoint: function(trackPoint) { + trackPoint.isOrigin = true; + this._trackPoints.push(trackPoint); + this._update(); + }, + + _update: function() { + this._sortTrackPointsByTime(); + this._updatetimeTick(); + }, + + // è½¨è¿¹ç‚¹æŒ‰æ—¶é—´æŽ’åº ã€å†’泡排åºã€‘ + _sortTrackPointsByTime: function() { + let len = this._trackPoints.length; + for (let i = 0; i < len; i++) { + for (let j = 0; j < len - 1 - i; j++) { + if (this._trackPoints[j].time > this._trackPoints[j + 1].time) { + let tmp = this._trackPoints[j + 1]; + this._trackPoints[j + 1] = this._trackPoints[j]; + this._trackPoints[j] = tmp; + } + } + } + }, + + // 为轨迹点建立时间索引,优化查找性能 + _updatetimeTick: function() { + this._timeTick = {}; + for (let i = 0, len = this._trackPoints.length; i < len; i++) { + this._timeTick[this._trackPoints[i].time] = i; + } + } +}); + +export const track = function(trackData, options) { + return new Track(trackData, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/trackcontroller.js b/src/track-playback/src/leaflet.trackplayback/trackcontroller.js new file mode 100644 index 0000000000000000000000000000000000000000..2e8157437f541500e2aa0774329d2c9969a7d6ce --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/trackcontroller.js @@ -0,0 +1,72 @@ +import L from "leaflet"; + +import { isArray } from "./util"; +import { Track } from "./track"; + +/** + * 控制器类 + * 控制轨迹和绘制 + */ +export const TrackController = L.Class.extend({ + initialize: function(tracks = [], draw, options) { + L.setOptions(this, options); + + this._tracks = []; + this.addTrack(tracks); + + this._draw = draw; + + this._updateTime(); + }, + + getMinTime: function() { + return this._minTime; + }, + + getMaxTime: function() { + return this._maxTime; + }, + + addTrack: function(track) { + if (isArray(track)) { + for (let i = 0, len = track.length; i < len; i++) { + this.addTrack(track[i]); + } + } else if (track instanceof Track) { + this._tracks.push(track); + this._updateTime(); + } else { + throw new Error( + "tracks must be an instance of `Track` or an array of `Track` instance!" + ); + } + }, + + drawTracksByTime: function(time) { + this._draw.clear(); + for (let i = 0, len = this._tracks.length; i < len; i++) { + let track = this._tracks[i]; + let tps = track.getTrackPointsBeforeTime(time); + if (tps && tps.length) this._draw.drawTrack(tps); + } + }, + + _updateTime: function() { + this._minTime = this._tracks[0].getStartTrackPoint().time; + this._maxTime = this._tracks[0].getEndTrackPoint().time; + for (let i = 0, len = this._tracks.length; i < len; i++) { + let stime = this._tracks[i].getStartTrackPoint().time; + let etime = this._tracks[i].getEndTrackPoint().time; + if (stime < this._minTime) { + this._minTime = stime; + } + if (etime > this._maxTime) { + this._maxTime = etime; + } + } + } +}); + +export const trackController = function(tracks, draw, options) { + return new TrackController(tracks, draw, options); +}; diff --git a/src/track-playback/src/leaflet.trackplayback/tracklayer.js b/src/track-playback/src/leaflet.trackplayback/tracklayer.js new file mode 100644 index 0000000000000000000000000000000000000000..a03027b93dcdfaba4ee183889de73c9d2cc28546 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/tracklayer.js @@ -0,0 +1,69 @@ +import L from 'leaflet' + +/** + * 轨迹图层 + */ +export const TrackLayer = L.Renderer.extend({ + + initialize: function (options) { + L.Renderer.prototype.initialize.call(this, options) + this.options.padding = 0.1 + }, + + onAdd: function (map) { + this._container = L.DomUtil.create('canvas', 'leaflet-zoom-animated') + + var pane = map.getPane(this.options.pane) + pane.appendChild(this._container) + + this._ctx = this._container.getContext('2d') + + this._update() + }, + + onRemove: function (map) { + L.DomUtil.remove(this._container) + }, + + getContainer: function () { + return this._container + }, + + getBounds: function () { + return this._bounds + }, + + _update: function () { + if (this._map._animatingZoom && this._bounds) { + return + } + + L.Renderer.prototype._update.call(this) + + var b = this._bounds + + var container = this._container + + var size = b.getSize() + + var m = L.Browser.retina ? 2 : 1 + + L.DomUtil.setPosition(container, b.min) + + // set canvas size (also clearing it); use double size on retina + container.width = m * size.x + container.height = m * size.y + container.style.width = size.x + 'px' + container.style.height = size.y + 'px' + + if (L.Browser.retina) { + this._ctx.scale(2, 2) + } + + // translate so we use the same path coordinates after canvas element moves + this._ctx.translate(-b.min.x, -b.min.y) + + // Tell paths to redraw themselves + this.fire('update') + } +}) diff --git a/src/track-playback/src/leaflet.trackplayback/trackplayback.js b/src/track-playback/src/leaflet.trackplayback/trackplayback.js new file mode 100644 index 0000000000000000000000000000000000000000..460102ff1b594c01fad7396cb9517366a04242f8 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/trackplayback.js @@ -0,0 +1,131 @@ +import L from 'leaflet' + +import { + Track +} from './track' +import { + TrackController +} from './trackcontroller' +import { + Clock +} from './clock' +import { + Draw +} from './draw' +import * as Util from './util' + +/** + * single track data + * [{lat: 30, lng: 116, time: 1502529980, heading: 300, info:[]},{},....] + * + * mutiple track data + * [single track data, single track data, single track data] + */ +export const TrackPlayBack = L.Class.extend({ + + includes: L.Mixin.Events, + + initialize: function (data, map, options = {}) { + let drawOptions = { + trackPointOptions: options.trackPointOptions, + trackLineOptions: options.trackLineOptions, + targetOptions: options.targetOptions, + toolTipOptions: options.toolTipOptions + } + this.tracks = this._initTracks(data) + this.draw = new Draw(map, drawOptions) + this.trackController = new TrackController(this.tracks, this.draw) + this.clock = new Clock(this.trackController, options.clockOptions) + + this.clock.on('tick', this._tick, this) + }, + start: function () { + this.clock.start() + return this + }, + stop: function () { + this.clock.stop() + return this + }, + rePlaying: function () { + this.clock.rePlaying() + return this + }, + slowSpeed: function () { + this.clock.slowSpeed() + return this + }, + quickSpeed: function () { + this.clock.quickSpeed() + return this + }, + getSpeed: function () { + return this.clock.getSpeed() + }, + getCurTime: function () { + return this.clock.getCurTime() + }, + getStartTime: function () { + return this.clock.getStartTime() + }, + getEndTime: function () { + return this.clock.getEndTime() + }, + isPlaying: function () { + return this.clock.isPlaying() + }, + setCursor: function (time) { + this.clock.setCursor(time) + return this + }, + setSpeed: function (speed) { + this.clock.setSpeed(speed) + return this + }, + showTrackPoint: function () { + this.draw.showTrackPoint() + return this + }, + hideTrackPoint: function () { + this.draw.hideTrackPoint() + return this + }, + showTrackLine: function () { + this.draw.showTrackLine() + return this + }, + hideTrackLine: function () { + this.draw.hideTrackLine() + return this + }, + dispose: function () { + this.clock.off('tick', this._tick) + this.draw.remove() + this.tracks = null + this.draw = null + this.trackController = null + this.clock = null + }, + _tick: function (e) { + this.fire('tick', e) + }, + _initTracks: function (data) { + let tracks = [] + if (Util.isArray(data)) { + if (Util.isArray(data[0])) { + // 多æ¡è½¨è¿¹ + for (let i = 0, len = data.length; i < len; i++) { + tracks.push(new Track(data[i])) + } + } else { + // å•æ¡è½¨è¿¹ + tracks.push(new Track(data)) + } + } + return tracks + } +}) + +export const trackplayback = function (data, map, options) { + return new TrackPlayBack(data, map, options) +} diff --git a/src/track-playback/src/leaflet.trackplayback/util.js b/src/track-playback/src/leaflet.trackplayback/util.js new file mode 100644 index 0000000000000000000000000000000000000000..b40984d26d198f2dcb8dade0f18eba8153d44d87 --- /dev/null +++ b/src/track-playback/src/leaflet.trackplayback/util.js @@ -0,0 +1,3 @@ +export function isArray (arr) { + return Array.isArray ? Array.isArray(arr) : Object.prototype.toString.call(arr) === '[object Array]' +}