Phi: refactor some map marker
- better aircraft marker symbol - aircraft symbol loaded from file, no longer hardcoded - marker as knockout component
This commit is contained in:
parent
dbc5d6689b
commit
2812cc6321
8 changed files with 168 additions and 139 deletions
|
@ -1,4 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet">
|
||||
<path d="M250.2,59.002c11.001,0,20.176,9.165,20.176,20.777v122.24l171.12,95.954v42.779l-171.12-49.501v89.227l40.337,29.946v35.446l-60.52-20.18-60.502,20.166v-35.45l40.341-29.946v-89.227l-171.14,49.51v-42.779l171.14-95.954v-122.24c0-11.612,9.15-20.777,20.16-20.777z" fill="deeppink" stroke="black" stroke-width="5"/>
|
||||
<defs>
|
||||
<filter id="aircraftShadow1749" x="0" y="0" width="200%" height="200%">
|
||||
<feOffset result="offOut" in="SourceAlpha" dx="20" dy="20" />
|
||||
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="10" />
|
||||
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
|
||||
</filter>
|
||||
</defs>
|
||||
<path d="M250.2,59.002c11.001,0,20.176,9.165,20.176,20.777v122.24l171.12,95.954v42.779l-171.12-49.501v89.227l40.337,29.946v35.446l-60.52-20.18-60.502,20.166v-35.45l40.341-29.946v-89.227l-171.14,49.51v-42.779l171.14-95.954v-122.24c0-11.612,9.15-20.777,20.16-20.777z" fill="deeppink" stroke="black" stroke-width="5" filter="url(#aircraftShadow1749)"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 834 B |
|
@ -454,6 +454,10 @@ require([
|
|||
require : 'widgets/radiostack'
|
||||
});
|
||||
|
||||
ko.components.register('AircraftMarker', {
|
||||
require : 'widgets/AircraftMarker'
|
||||
});
|
||||
|
||||
ko.components.register('METAR', {
|
||||
require : 'widgets/metar'
|
||||
});
|
||||
|
|
|
@ -2,13 +2,20 @@
|
|||
if (typeof define === "function" && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([
|
||||
'leaflet', 'props', './MapIcons'
|
||||
'knockout', 'leaflet', 'props'
|
||||
], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
factory();
|
||||
}
|
||||
}(function(leaflet, SGPropertyNode, MAP_ICON) {
|
||||
}(function(ko, leaflet, SGPropertyNode ) {
|
||||
|
||||
function ViewModel(h,l1,l2) {
|
||||
var self = this;
|
||||
|
||||
self.heading = h;
|
||||
self.labelLines = [ l1,l2 ];
|
||||
}
|
||||
|
||||
leaflet.AILayer = leaflet.GeoJSON.extend({
|
||||
options : {
|
||||
|
@ -20,27 +27,22 @@
|
|||
};
|
||||
|
||||
if (feature.properties.type == "aircraft" || feature.properties.type == "multiplayer") {
|
||||
options.angle = feature.properties.heading;
|
||||
options.icon = MAP_ICON["aircraft"];
|
||||
}
|
||||
return new leaflet.RotatedMarker(latlng, options);
|
||||
},
|
||||
|
||||
onEachFeature : function(feature, layer) {
|
||||
if (feature.properties) {
|
||||
var popupString = '<div class="popup">';
|
||||
for ( var k in feature.properties) {
|
||||
var v = feature.properties[k];
|
||||
popupString += k + ': ' + v + '<br />';
|
||||
}
|
||||
popupString += '</div>';
|
||||
layer.bindPopup(popupString, {
|
||||
maxHeight : 200
|
||||
});
|
||||
var l1 = feature.properties.callsign,
|
||||
l2 = feature.properties.heading + 'T ' + feature.properties.speed + 'KTAS ' +
|
||||
'F' + (feature.geometry.coordinates[2]/100).toFixed(0);
|
||||
var m = L.aircraftMarker(latlng);
|
||||
m.on('add', function(e) {
|
||||
ko.applyBindings( new ViewModel(feature.properties.heading,l1,l2), e.target._icon);
|
||||
});
|
||||
return m;
|
||||
}
|
||||
return new leaflet.Marker(latlng, options);
|
||||
},
|
||||
|
||||
// onEachFeature : function(feature, layer) {
|
||||
// },
|
||||
},
|
||||
|
||||
onAdd : function(map) {
|
||||
leaflet.GeoJSON.prototype.onAdd.call(this, map);
|
||||
this.update(++this.updateId);
|
||||
|
@ -67,22 +69,21 @@
|
|||
self.clearLayers();
|
||||
self.addData(self.aiPropsToGeoJson(data, [
|
||||
"aircraft", "multiplayer", "carrier"
|
||||
]));
|
||||
], self._map.getBounds()));
|
||||
}).fail(function(a, b) {
|
||||
self.updateId++;
|
||||
console.log(a, b);
|
||||
alert('failed to load AI data');
|
||||
}).always(function() {
|
||||
});
|
||||
|
||||
if (self.updateId == id) {
|
||||
setTimeout(function() {
|
||||
self.update(id)
|
||||
self.update(id)
|
||||
}, 10000);
|
||||
}
|
||||
},
|
||||
|
||||
aiPropsToGeoJson : function(props, types) {
|
||||
aiPropsToGeoJson : function(props, types, bounds ) {
|
||||
var geoJSON = {
|
||||
type : "FeatureCollection",
|
||||
features : [],
|
||||
|
@ -100,7 +101,10 @@
|
|||
var velocities = child.getNode("velocities");
|
||||
var lon = position.getNode("longitude-deg").getValue();
|
||||
var lat = position.getNode("latitude-deg").getValue();
|
||||
var alt = position.getNode("altitude-ft") * 0.3048;
|
||||
if( false == bounds.contains(L.latLng(lat,lon)) ) {
|
||||
return;
|
||||
}
|
||||
var alt = position.getNode("altitude-ft").getValue();
|
||||
var heading = orientation.getNode("true-heading-deg").getValue();
|
||||
var id = child.getNode("id").getValue();
|
||||
var callsign = "";
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
return new leaflet.RotatedMarker(latlng, options);
|
||||
return new leaflet./*Rotated*/Marker(latlng, options);
|
||||
},
|
||||
|
||||
onEachFeature : function(feature, layer) {
|
||||
|
|
24
Phi/widgets/AircraftMarker.html
Normal file
24
Phi/widgets/AircraftMarker.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<div data-bind="
|
||||
html: iconSvg,
|
||||
style: {
|
||||
color: 'red',
|
||||
transform: transformCss(),
|
||||
'-webkit-transform': transformCss(),
|
||||
'-ms-transform': transformCss(),
|
||||
}">
|
||||
</div>
|
||||
<!-- ko foreach: label -->
|
||||
<div data-bind="text: $data" style="
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
text-shadow: 0px 1px 0px black;
|
||||
line-height: 105%;
|
||||
border: 1px solid rgba(0,0,0,0.20);
|
||||
background: rgba(40,40,40,0.2);
|
||||
padding: 2px 3px;
|
||||
">
|
||||
</div>
|
||||
<!-- /ko -->
|
41
Phi/widgets/AircraftMarker.js
Normal file
41
Phi/widgets/AircraftMarker.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
define([
|
||||
'jquery', 'knockout', 'text!./AircraftMarker.html', 'text!../images/aircraft.svg'
|
||||
], function(jquery, ko, htmlString, aircraftSvgFileContent ) {
|
||||
|
||||
// extract root element which should be <svg> from image xml (strip pi) and convert to string
|
||||
// what an easy way to make Safari happy :-P
|
||||
var iconSvgString = jquery('<div>') // wrap into detached <div> to get it's innerHTML
|
||||
.append(
|
||||
jquery( aircraftSvgFileContent ) // parse the file content
|
||||
.filter(":first")[0]) // get root element
|
||||
.html();
|
||||
|
||||
function ViewModel(params) {
|
||||
var self = this;
|
||||
|
||||
self.iconSvg = iconSvgString;
|
||||
self.rotate = 0;
|
||||
self.label = [];
|
||||
|
||||
if( params && params.rotate ) {
|
||||
self.rotate = params.rotate;
|
||||
}
|
||||
|
||||
if( params && params.label ) {
|
||||
self.label = params.label;
|
||||
}
|
||||
|
||||
self.transformCss = function() {
|
||||
return 'rotate(' + ko.unwrap(self.rotate) + 'deg)';
|
||||
}
|
||||
}
|
||||
|
||||
// ViewModel.prototype.dispose = function() {
|
||||
// }
|
||||
|
||||
// Return component definition
|
||||
return {
|
||||
viewModel : ViewModel,
|
||||
template : htmlString
|
||||
};
|
||||
});
|
|
@ -7,33 +7,25 @@
|
|||
fill: rgba(0, 255, 0, 0.4);
|
||||
}
|
||||
|
||||
.aircraft-marker-popup .leaflet-popup-tip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aircraft-marker-popup .leaflet-popup-content-wrapper {
|
||||
background-color: rgba(255, 255, 255, 0.50);
|
||||
}
|
||||
|
||||
.aircraft-marker-popup .leaflet-popup-content {
|
||||
margin: 5px 5px;
|
||||
}
|
||||
|
||||
.aircraft-marker-altitude {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.aircraft-marker-heading {
|
||||
text-align: center;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.aircraft-marker {
|
||||
float: left;
|
||||
color: #00ff00;
|
||||
text-shadow: 1px 1px #404040;
|
||||
}
|
||||
|
||||
.aircraft-marker-label {
|
||||
white-space: nowrap;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
text-shadow: 0px 1px 0px black;
|
||||
line-height: 110%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
padding: 2px 3px;
|
||||
display: inline-block;
|
||||
color: rgb(255,255,255);
|
||||
}
|
||||
|
||||
.aircraft-marker span {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,56 @@
|
|||
define(
|
||||
[
|
||||
'knockout', 'jquery', 'leaflet', 'text!./map.html', 'text!../images/aircraft.svg'
|
||||
'knockout', 'jquery', 'leaflet', 'text!./map.html'
|
||||
],
|
||||
function(ko, jquery, leaflet, htmlString, aircraftSvg ) {
|
||||
function(ko, jquery, leaflet, htmlString ) {
|
||||
if( !L.AircraftMarker ) {
|
||||
L.AircraftMarker = L.Marker
|
||||
.extend({
|
||||
options : {
|
||||
clickable : false,
|
||||
keyboard : false,
|
||||
icon : L
|
||||
.divIcon({
|
||||
iconSize : [
|
||||
60, 60
|
||||
],
|
||||
iconAnchor : [
|
||||
30, 30
|
||||
],
|
||||
className : 'aircraft-marker-icon',
|
||||
html :
|
||||
'<div data-bind="component: { ' +
|
||||
'name: \'AircraftMarker\', ' +
|
||||
'params: { rotate: heading, label: labelLines } ' +
|
||||
'}"></div>',
|
||||
}),
|
||||
zIndexOffset : 10000,
|
||||
updateInterval : 100,
|
||||
},
|
||||
|
||||
initialize : function(latlng, options) {
|
||||
L.Marker.prototype.initialize(latlng, options);
|
||||
L.Util.setOptions(this, options);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
L.aircraftMarker = function(latlng, options) {
|
||||
return new L.AircraftMarker(latlng, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ViewModel(params, componentInfo) {
|
||||
var self = this;
|
||||
|
||||
{ // extract <svg> element from image xml (strip pi)
|
||||
var xmlDoc = jquery.parseXML( aircraftSvg );
|
||||
aircraftSvg = jquery( xmlDoc ).find("svg")[0].outerHTML;
|
||||
}
|
||||
|
||||
self.element = componentInfo.element;
|
||||
self.followAircraft = ko.observable(true);
|
||||
|
||||
self.toggleFollowAircraft = function(a) {
|
||||
self.followAircraft(!self.followAircraft());
|
||||
}
|
||||
|
||||
|
||||
self.altitude = ko.observable(0).extend({
|
||||
fgprop : 'altitude'
|
||||
});
|
||||
|
@ -27,10 +59,6 @@ define(
|
|||
fgprop : 'groundspeed'
|
||||
});
|
||||
|
||||
self.heading = ko.observable(0).extend({
|
||||
fgprop : 'heading'
|
||||
});
|
||||
|
||||
if (params && params.css) {
|
||||
for ( var p in params.css) {
|
||||
jquery(self.element).css(p, params.css[p]);
|
||||
|
@ -101,78 +129,6 @@ define(
|
|||
L.control.scale(params.scale).addTo(self.map);
|
||||
}
|
||||
|
||||
L.RotatedMarker = L.Marker.extend({
|
||||
options : {
|
||||
angle : 0
|
||||
},
|
||||
|
||||
_setPos : function(pos) {
|
||||
L.Marker.prototype._setPos.call(this, pos);
|
||||
this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
|
||||
}
|
||||
});
|
||||
|
||||
L.AircraftMarker = L.RotatedMarker
|
||||
.extend({
|
||||
options : {
|
||||
angle : 0,
|
||||
clickable : false,
|
||||
keyboard : false,
|
||||
icon : L
|
||||
.divIcon({
|
||||
iconSize : [
|
||||
60, 60
|
||||
],
|
||||
iconAnchor : [
|
||||
30, 30
|
||||
],
|
||||
className : 'aircraft-marker-icon',
|
||||
html : aircraftSvg,
|
||||
}),
|
||||
zIndexOffset : 10000,
|
||||
updateInterval : 100,
|
||||
},
|
||||
|
||||
initialize : function(latlng, options) {
|
||||
L.RotatedMarker.prototype.initialize(latlng, options);
|
||||
L.Util.setOptions(this, options);
|
||||
},
|
||||
|
||||
onAdd : function(map) {
|
||||
L.RotatedMarker.prototype.onAdd.call(this, map);
|
||||
this.popup = L.popup({
|
||||
autoPan : false,
|
||||
keepInView : false,
|
||||
closeButton : false,
|
||||
className : 'aircraft-marker-popup',
|
||||
closeOnClick : false,
|
||||
maxWidth : 200,
|
||||
minWidth : 120,
|
||||
offset : [
|
||||
30, 30
|
||||
],
|
||||
}, this);
|
||||
this.popup
|
||||
.setContent('<div class="aircraft-marker aircraft-marker-altitude"><span data-bind="text: altitude().toFixed(0)"></span>ft</div>'
|
||||
+ '<div class="aircraft-marker aircraft-marker-heading"><span data-bind="text: heading().toFixed(0)"></span>°</div>'
|
||||
+ '<div class="aircraft-marker aircraft-marker-tas"><span data-bind="text: tas().toFixed(0)"></span>kt</div><div style="clear: both"/>');
|
||||
this.bindPopup(this.popup);
|
||||
this.addTo(this._map);
|
||||
this.openPopup();
|
||||
},
|
||||
|
||||
onRemove : function(map) {
|
||||
if (this.timeoutid != null)
|
||||
clearTimeout(this.timeoutid);
|
||||
L.RotatedMarker.prototype.onRemove.call(this, map);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
L.aircraftMarker = function(latlng, options) {
|
||||
return new L.AircraftMarker(latlng, options);
|
||||
}
|
||||
|
||||
var aircraftMarker = L.aircraftMarker(self.map.getCenter());
|
||||
|
||||
aircraftMarker.addTo(self.map);
|
||||
|
@ -203,13 +159,15 @@ define(
|
|||
aircraftMarker.setLatLng(newValue);
|
||||
});
|
||||
|
||||
self.heading.subscribe(function(newValue) {
|
||||
var h = Math.round( newValue );
|
||||
if( aircraftMarker.options.angle != h ) {
|
||||
aircraftMarker.options.angle = h;
|
||||
aircraftMarker.setLatLng(self.position());
|
||||
}
|
||||
});
|
||||
self.labelLines = [
|
||||
'You',
|
||||
ko.pureComputed(function() {
|
||||
var h = Math.round(self.heading());
|
||||
var t = Math.round(self.tas());
|
||||
var a = Math.round(self.altitude());
|
||||
return '' + h + "T " + t + "KTAS " + a + "ft";
|
||||
}),
|
||||
];
|
||||
|
||||
self.mapCenter = ko.pureComputed(function() {
|
||||
return leaflet.latLng(self.latitude(), self.longitude());
|
||||
|
@ -233,7 +191,6 @@ define(
|
|||
|
||||
var center = leaflet.latLng(self.latitude(), self.longitude());
|
||||
self.map.setView( center );
|
||||
aircraftMarker.options.angle = self.heading();
|
||||
aircraftMarker.setLatLng(center);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue