433 lines
15 KiB
JavaScript
433 lines
15 KiB
JavaScript
/* The defition of display styles needed by openlayersmap.js */
|
|
var DisplayStyles = new Map();
|
|
|
|
|
|
|
|
_acIconStyleCache = {}; // cache for icon objects (TODO: move this into separate object which can be destroyed on view-change)
|
|
DisplayStyles.set("standard", {
|
|
name:"OSM Standard",
|
|
shortname:"standard",
|
|
defaultTrackLookbackSecs:undefined,
|
|
acIconStyle: function(){
|
|
feature = this;
|
|
if(selfDestruct(feature)) return;
|
|
|
|
var ac = planes.get(feature.hex);
|
|
var acStyleProperties = getAircraftStyleProperties(ac);
|
|
|
|
if(ac.cat==""){
|
|
ac.cat="A2"; // default: medium jet
|
|
}
|
|
var iconProps = get_image_object(ac.cat, ac.typ);
|
|
|
|
var lineDashJoined;
|
|
if(acStyleProperties.lineDash != undefined){
|
|
lineDashJoined = acStyleProperties.lineDash.join(" ");
|
|
} else {
|
|
lineDashJoined = "";
|
|
}
|
|
|
|
var borderRgbaSplitted = acStyleProperties.borderCol.replace(/^(rgb|rgba)\(/,'').replace(/\)$/,'').replace(/\s/g,'').split(',');
|
|
var borderCol = 'rgb('+borderRgbaSplitted[0]+','+borderRgbaSplitted[1]+','+borderRgbaSplitted[2]+')';
|
|
var borderAlpha = borderRgbaSplitted[3];
|
|
|
|
|
|
// find approximation to center of svg icon, the center is patched to the right position already by an include
|
|
var bbox = Raphael.pathBBox(iconProps.path);
|
|
var x = Math.floor(bbox.x + bbox.width/2.0);
|
|
var y = Math.floor(bbox.y + bbox.height/2.0);
|
|
var center = {'x': x,'y':y };
|
|
|
|
|
|
// this applies the scaling of all points in the svg path so the resulting bounding box should not be larger than 32x32 (restriction by gmaps api)
|
|
// matrix transform is faster than mapPath()
|
|
|
|
offs_x = 0;
|
|
if(iconProps.ofX){
|
|
offs_x = iconProps.ofX;
|
|
}
|
|
offs_y = 0;
|
|
if(iconProps.ofY){
|
|
offs_y = iconProps.ofY;
|
|
}
|
|
var transform = "T"+(-1*center.x + offs_x)+","+(-1*center.y + offs_y)+"S"+iconProps.scale;
|
|
var newPath = Raphael.mapPath(iconProps.path, Raphael.toMatrix(iconProps.path, transform));
|
|
|
|
// recalculate bounding box
|
|
var object_box = Raphael.pathBBox(newPath);
|
|
|
|
|
|
var strokew = 1;
|
|
if (ac.hex == highlightedPlane) { strokew = 2; }
|
|
if (ac.timeout == 2) { strokew = 0.333; } /* light on timeout 2 */
|
|
|
|
|
|
// use a viewbox that has its top left point at the top left point of the geometry's bounding box
|
|
// as specified by the google maps js api, a symbol has to fit a 32x32 coordinate system, hence the size
|
|
var viewBox = 'viewBox="'+[-16, -16, 32, 32].join(' ')+'"';
|
|
var svgDynProps = {
|
|
fill : getAircraftFillColor(ac),
|
|
stroke : borderCol,
|
|
strokeOpc : borderAlpha,
|
|
strokeWdt : strokew,
|
|
strokeDash: lineDashJoined,
|
|
//rotationAngle: rotationAngle,
|
|
}
|
|
|
|
var generateSvgString = function(addition){
|
|
var pathplain = '<path style="fill:'+svgDynProps.fill+';stroke:'+svgDynProps.stroke+';stroke-opacity:'+svgDynProps.strokeOpc+';stroke-width:'+svgDynProps.strokeWdt+';'+((svgDynProps.strokeDash)?'stroke-dasharray:'+svgDynProps.strokeDash+';':"")+'" fill-opacity="0.9" d="'+newPath+'" '+addition+'/>';
|
|
var svgplain = '<svg version="1.1" shape-rendering="geometricPrecision" width="32px" height="32px" '+viewBox+' xmlns="http://www.w3.org/2000/svg">'+pathplain+'</svg>';
|
|
return svgplain;
|
|
}
|
|
|
|
|
|
rotationAngle = ((ac.trk + iconProps.rotcorr)%360) / 180 * Math.PI;
|
|
|
|
var svgdat = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(generateSvgString(""));
|
|
|
|
// retrieve icon from cache
|
|
var styleBundle = _acIconStyleCache[feature.hex];
|
|
var style = undefined;
|
|
|
|
var cachedSvgDynProps = styleBundle ? styleBundle['svgprops'] : undefined;
|
|
if(styleBundle && cachedSvgDynProps && (JSON.stringify(cachedSvgDynProps) === JSON.stringify(svgDynProps))){
|
|
style = styleBundle['style'];
|
|
// now check if we have other requirements than the style has properties
|
|
childImg = style.getImage();
|
|
/*if(childImg.getScale() != iconProps.scale){
|
|
childImg.setScale(5.0/iconProps.scale);
|
|
}*/
|
|
if(childImg.getRotation() != rotationAngle){
|
|
childImg.setRotation(rotationAngle);
|
|
}
|
|
|
|
} else {
|
|
var iconEdgeLen = 32; //parseFloat($("body").css("font-size"))*2.9; // calculate icon size in pixels
|
|
|
|
style = new ol.style.Style( {image: new ol.style.Icon({
|
|
snapToPixel: false,
|
|
//anchor: [center.x, center.y],
|
|
//anchor: [fractX, fractY],
|
|
//anchorOrigin: 'top-left',
|
|
//anchorXUnits: 'pixels',
|
|
//anchorYUnits: 'pixels',
|
|
src: svgdat,
|
|
rotation: rotationAngle,
|
|
imgSize: [iconEdgeLen, iconEdgeLen] // only necessary because of IE11
|
|
})
|
|
});
|
|
_acIconStyleCache[feature.hex] = {
|
|
style: style,
|
|
svgprops: svgDynProps
|
|
};
|
|
}
|
|
|
|
return [style];
|
|
}, // end acIconStyle
|
|
trackMissingStyle : function(coords){
|
|
return generateTrackMissingStyle(coords, 'red', 15, '1.3em');
|
|
},
|
|
// standard-skin
|
|
getInfoText: function(ac, vRateIndicator){
|
|
var label = "";
|
|
label += "<div data-mouseover=\""+ labelDataGetMouseover(ac) +"\">"; //+(ac.reg==null?"":ac.reg);
|
|
label += "<table>";
|
|
label += "<tr><td class=\"flight\" colspan=\"2\">" + labelDataID(ac) + " </td><td>" + (ac.reg==null?"":ac.reg) + "</td></tr>";
|
|
label += "<tr>";
|
|
label += "<td class=\"altitude\">" + vRateIndicator + labelDataAltitude(ac) + "</td>";
|
|
label += "<td class=\"verticalRate\">" + ' ' + labelDataVerticalRate(ac, "Δ") + "</td><td class=\"speed\"><i class=\"icon ion-speedometer\"></i>" + labelDataSpeed(ac) + "</td>";
|
|
label += "</tr></table></div>";
|
|
|
|
return label;
|
|
},
|
|
drawAllTracks: false, // do not draw tracks of all airplanes in sight
|
|
datasourceColorMap: {
|
|
"A" : 'rgba(0, 0, 0, 0.5)', //ADS-B -- black
|
|
"M" : 'rgba(235,49,49,1)', //MLAT -- red
|
|
"F" : 'rgba(0,170,0,1)', //FlightAware -- green
|
|
"O" : 'rgba(209, 87, 209,1)',// OGN/Flarm -- pink
|
|
"L" : 'rgba(146, 22, 146,1)'// Jetvision Flarm -- darkmagenta
|
|
},
|
|
highlightedColor: 'rgba(247,110,12,1)',
|
|
vrateColorMap: {
|
|
"climb": 'rgba(25,165,166,1)',
|
|
"descend": 'rgba(185,92,0,1)',
|
|
"const": 'rgba(255,255,0,1)',
|
|
"timeout": 'rgba(128,128,128,1)'
|
|
}
|
|
});
|
|
DisplayStyles.set("atcscope", {
|
|
name:"ATC Scope Display",
|
|
shortname:"atcscope",
|
|
drawAllTracks: true, // draw tracks of all airplanes in sight
|
|
defaultTrackLookbackSecs: 100, // draw a history of n seconds for un-highlighted planes (counted from newest datapoint)
|
|
acIconStyle: function(){
|
|
feature = this;
|
|
if(selfDestruct(feature)) return;
|
|
|
|
var ac = planes.get(feature.hex);
|
|
|
|
//current ac coordinates, origin for track indicating vector
|
|
//var acCoord = ol.proj.fromLonLat([parseFloat(ac.lon), parseFloat(ac.lat)]);
|
|
var acCoordWGS84 = [parseFloat(ac.lon), parseFloat(ac.lat)];
|
|
var acTrackDirWGS84 = acCoordWGS84.slice(); // clone accoords
|
|
// var acTrackDirection = ol.proj.fromLonLat([parseFloat(ac.lon), parseFloat(ac.lat)]);
|
|
//calculate the other end of the track indicating vector
|
|
|
|
// get ac track in rad, origin is heading north
|
|
var angle_to_rotate = (ac.trk / 180) * Math.PI;
|
|
|
|
// move a bit to create a vector to acCoords
|
|
var offset = 0.5;
|
|
ol.coordinate.add(acTrackDirWGS84,[offset,offset]);
|
|
// get vector length in meters
|
|
var wgs84Sphere= new ol.Sphere(6378137);
|
|
var vLength = wgs84Sphere.haversineDistance(acCoordWGS84, acTrackDirWGS84);
|
|
|
|
var kt_in_ms = 0.514444; // 1kt = 0.51444.. m/s
|
|
var acMeters_per_min = (ac.spd * kt_in_ms) * 60; // distance the ac travels in 1min
|
|
|
|
var vScaleFactor = acMeters_per_min/vLength;
|
|
|
|
// reset to acCoords and apply actual translation
|
|
acTrackDirWGS84 = acCoordWGS84.slice();
|
|
ol.coordinate.add(acTrackDirWGS84,[offset*vScaleFactor,offset*vScaleFactor]);
|
|
|
|
|
|
var acCoord = ol.proj.fromLonLat(acCoordWGS84);
|
|
var acTrackDirection = ol.proj.fromLonLat(acTrackDirWGS84);
|
|
|
|
// angle correction so vector points up
|
|
var angle_to_north = Math.atan(
|
|
(acCoord[1]-acTrackDirection[1])/(acCoord[0]-acTrackDirection[0])
|
|
) - (0.5 * Math.PI);
|
|
|
|
var indicatingVector = new ol.geom.LineString([acCoord, acTrackDirection]);
|
|
|
|
// rotation is clockwise
|
|
indicatingVector.rotate(-1.0 * angle_to_north - angle_to_rotate, acCoord);
|
|
|
|
var commonFill = new ol.style.Fill({
|
|
color: 'rgba(255,255,255,0.0)'
|
|
});
|
|
|
|
var acVector = new ol.style.Style({
|
|
geometry: indicatingVector,
|
|
fill: commonFill,
|
|
//color of the track-vector
|
|
stroke : new ol.style.Stroke({
|
|
color : 'rgba(98,255,119,1)',
|
|
width : 2
|
|
})
|
|
});
|
|
var acSquare = new ol.style.Style({
|
|
image: new ol.style.RegularShape({
|
|
fill: new ol.style.Fill({
|
|
color: getAircraftFillColor(ac)
|
|
}),
|
|
stroke: new ol.style.Stroke({
|
|
color: getActiveDisplaystyle().datasourceColorMap[ac.src],
|
|
width: 1
|
|
}),
|
|
points: 4,
|
|
radius: 4,
|
|
angle: Math.PI / 4
|
|
})
|
|
});
|
|
|
|
return [acVector, acSquare];
|
|
},
|
|
trackMissingStyle : function(coords){
|
|
return generateTrackMissingStyle(coords, 'green', 10, '0.8em');
|
|
},
|
|
// atcscope skin
|
|
getInfoText: function(ac, vRateIndicator){
|
|
var label = "";
|
|
// label += "<div data-mouseover=\""+ labelDataGetMouseover(ac) +"\">";
|
|
// label += "<table style=\"width: 100%;\">";
|
|
// label += "<tr><td class=\"infoLabel-centercell\" colspan=\"2\">" + (ac.fli || ("~" + ac.hex)) + "</td></tr>";
|
|
// label += "<tr>";
|
|
// label += "<td class=\"infoLabel-leftcell\">" + labelDataAltitude(ac) +" </td>";
|
|
// label += "<td class=\"infoLabel-rightcell\">"+vRateIndicator + labelDataVerticalRate(ac) + "</td></tr>";
|
|
// label+= "<tr><td colspan=\"2\" class=\"infoLabel-centercell\">" + labelDataSpeed(ac) + "</td></tr>";
|
|
// label += "</table></div>";
|
|
label += "<div data-mouseover=\""+ labelDataGetMouseover(ac) +"\">"; //+(ac.reg==null?"":ac.reg);
|
|
label += "<table>";
|
|
label += "<tr><td class=\"flight\" colspan=\"2\">" + labelDataID(ac) + " </td><td>" + (ac.reg==null?"":ac.reg) + "</td></tr>";
|
|
label += "<tr>";
|
|
label += "<td class=\"altitude\">" + vRateIndicator + labelDataAltitude(ac) + "</td>";
|
|
label += "<td class=\"verticalRate\">" + ' ' + labelDataVerticalRate(ac, "Δ") + "</td><td class=\"speed\"><i class=\"icon ion-speedometer\"></i>" + labelDataSpeed(ac) + "</td>";
|
|
label += "</tr></table></div>";
|
|
|
|
return label;
|
|
},
|
|
// spare color: 232,183,12
|
|
datasourceColorMap: {
|
|
"A" : 'rgba(97, 76, 250,0.8)', //ADS-B bluish
|
|
"M" : 'rgba(255, 37, 26, 0.8)', //MLAT red
|
|
"F" : 'rgba(47, 255, 176, 0.8)', //FlightAware green
|
|
"O" : 'rgba(209, 87, 209,1)',// OGN/Flarm pinkish/light magenta
|
|
"L" : 'rgba(146, 22, 146,1)'// Jetvision Flarm dark intense magenta
|
|
},
|
|
highlightedColor: 'rgba(247,110,12,1)',
|
|
vrateColorMap: {
|
|
"climb": 'rgba(25,165,166,1)',
|
|
"descend": 'rgba(185,92,0,1)',
|
|
"const": 'rgba(255,255,255,0.0)',
|
|
"timeout": 'rgba(128,128,128,1)'
|
|
}
|
|
});
|
|
|
|
function getAircraftStyleProperties(plane){
|
|
var styleProperties = {
|
|
borderCol: undefined,
|
|
lineDash:undefined,
|
|
featureScale:undefined
|
|
};
|
|
|
|
styleProperties.borderCol = getActiveDisplaystyle().datasourceColorMap[plane.src];
|
|
//styleProperties.lineDash = [5,10];
|
|
|
|
if (plane.timeout == 1){ /* after timeout 1 */
|
|
styleProperties.borderCol = 'rgba(128,128,128,1)';
|
|
}
|
|
|
|
styleProperties.featureScale = function(){
|
|
var defaultScale = 1.0;
|
|
/* var myZoom = map.getView().getZoom();
|
|
if((myZoom <= 14) && ((6 < myZoom))) {
|
|
return defaultScale + 0.2 * ((myZoom-10)/4);
|
|
}
|
|
else if(myZoom < 14) {
|
|
return defaultScale - 0.2;
|
|
}
|
|
else {
|
|
return defaultScale + 0.2;
|
|
}*/
|
|
}();
|
|
|
|
return styleProperties;
|
|
}
|
|
|
|
function getAircraftFillColor(plane){
|
|
var fill = undefined;
|
|
|
|
/* Change color for climb/decent */
|
|
if (plane.vrt > 300)
|
|
{
|
|
/*rFill = 25; gFill = 165; bFill = 166;*/
|
|
fill = getActiveDisplaystyle().vrateColorMap["climb"];
|
|
}
|
|
else if (plane.vrt < -300)
|
|
{
|
|
/* original A320: rFill = 148; gFill = 91; bFill = 1; */
|
|
/*rFill = 228; gFill = 137; bFill = 0;*/
|
|
// last color used before hex conversion: rFill = 185; gFill = 92; bFill = 0;
|
|
fill = getActiveDisplaystyle().vrateColorMap["descend"];
|
|
} else {
|
|
fill = getActiveDisplaystyle().vrateColorMap["const"]; // transparent if no significant vrate
|
|
}
|
|
|
|
if (plane.timeout == 1) /* after timeout 1 */
|
|
{
|
|
fill = getActiveDisplaystyle().vrateColorMap["timeout"];
|
|
}
|
|
return fill;
|
|
}
|
|
|
|
// generates a style with four question marks around coords
|
|
function generateTrackMissingStyle(coords, textColor, radius, fontSize){
|
|
var off = radius; // alignment offset
|
|
var offsets = [
|
|
[0,off],
|
|
[0,off*-1],
|
|
[off,0],
|
|
[off*-1,0]
|
|
];
|
|
var styleArr = [];
|
|
offsets.forEach(function(offset){
|
|
styleArr.push(new ol.style.Style({
|
|
text: new ol.style.Text({
|
|
text:'?',
|
|
offsetX: offset[0],
|
|
offsetY: offset[1],
|
|
font: fontSize+' sans-serif',
|
|
fill: new ol.style.Fill({color: textColor})
|
|
}),
|
|
}));
|
|
});
|
|
return styleArr;
|
|
}
|
|
|
|
/*
|
|
* Check if feature still got a position update, if not destroy map artifacts
|
|
* */
|
|
function selfDestruct(feature){
|
|
if(!feature) return false; // cannot destruct nonexisting feature
|
|
|
|
var acHasFeaturesInCurrentExtent = keepPathsFromEviction.has(feature.hex);
|
|
|
|
if(!inLastUpdate.get(feature.hex) && !acHasFeaturesInCurrentExtent) {
|
|
removePlaneFeatures(planes.get(feature.hex));
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/* from gmap */
|
|
function labelDataGetMouseover(ac){
|
|
var title = ac.hex + " " + (ac.typ==null?"":ac.typ);
|
|
if ( ac.org!=null && ac.org.length ) { title += '<br/>' + ac.org; }
|
|
if ( ac.dst!=null && ac.dst.length ) { title += '→' + ac.dst; }
|
|
if ( ac.squ!=null && ac.squ.length ) {title += '<br/>SQ:' + ac.squ; }
|
|
return title;
|
|
}
|
|
|
|
function labelDataID(ac) {
|
|
if( ac.fli!==null && ac.fli!=="") return ac.fli;
|
|
if( ac.reg!==null && ac.reg!=="" && ac.reg!=="(state)" ) return ac.reg;
|
|
return ac.hex;
|
|
}
|
|
function labelDataVerticalRate(ac, prefix) {
|
|
var s = "";
|
|
if(prefix != "" && prefix != undefined){
|
|
s+= prefix;
|
|
}
|
|
|
|
// Vertical Rate
|
|
if (ac.vrt > -9000) {
|
|
/*if (ac.vrt < -300) s = */
|
|
s+= Math.round(ac.vrt/100);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function labelDataAltitude(ac) {
|
|
var s = "";
|
|
|
|
// Aircraft on Ground
|
|
if (ac.gda == "G")
|
|
{
|
|
s = "GND";
|
|
}
|
|
else
|
|
{
|
|
// Altitude
|
|
s = Math.round(ac.alt/100);
|
|
if (s < 0)
|
|
s = "-0" + -1*s;
|
|
else {
|
|
if (s < 10) s = "00" + s; else
|
|
if (s < 100) s = "0" + s;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function labelDataSpeed(ac) {
|
|
return Math.round(ac.spd/10);
|
|
}
|