/* 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 = ''; var svgplain = ''+pathplain+''; 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 += "
"; //+(ac.reg==null?"":ac.reg); label += ""; label += ""; label += ""; label += ""; label += ""; label += "
" + labelDataID(ac) + " " + (ac.reg==null?"":ac.reg) + "
" + vRateIndicator + labelDataAltitude(ac) + "" + ' ' + labelDataVerticalRate(ac, "Δ") + "" + labelDataSpeed(ac) + "
"; 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 += "
"; // label += ""; // label += ""; // label += ""; // label += ""; // label += ""; // label+= ""; // label += "
" + (ac.fli || ("~" + ac.hex)) + "
" + labelDataAltitude(ac) +" "+vRateIndicator + labelDataVerticalRate(ac) + "
" + labelDataSpeed(ac) + "
"; label += "
"; //+(ac.reg==null?"":ac.reg); label += ""; label += ""; label += ""; label += ""; label += ""; label += "
" + labelDataID(ac) + " " + (ac.reg==null?"":ac.reg) + "
" + vRateIndicator + labelDataAltitude(ac) + "" + ' ' + labelDataVerticalRate(ac, "Δ") + "" + labelDataSpeed(ac) + "
"; 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 += '
' + ac.org; } if ( ac.dst!=null && ac.dst.length ) { title += '→' + ac.dst; } if ( ac.squ!=null && ac.squ.length ) {title += '
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); }