Create a Custom Info Panel Content View
Create a custom info panel content view associated with a custom map content source. The following example will setup a vector content source that loads airport observations and renders their latest flight rules as point markers on an interactive map. It then configures a custom airport
content view to display within the application info panel when an airport’s marker is selected.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xweather JavaScript SDK - Interactive Map App w/Custom Info Panel</title>
<script defer src="https://cdn.aerisapi.com/sdk/js/latest/aerisweather.min.js"></script>
<link rel="stylesheet" href="https://cdn.aerisapi.com/sdk/js/latest/aerisweather.css">
<style>
#app {
font-family: 'Helvetica','Arial',sans-serif;
height: 600px;
margin: 30px auto;
width: 1000px;
}
.awxjs__app__ui-panel-info-content .flightrule,
.awxjs__app__ui-panel-info-content .flight-status {
font-size: 16px;
font-weight: bold;
}
.awxjs__app__ui-panel-info-content .flightrule .value,
.awxjs__app__ui-panel-info-content .flight-status .value {
font-size: 20px;
font-weight: bold;
padding-right: 35px;
position: relative;
text-align: right;
}
.awxjs__app__ui-panel-info-content .flightrule .indicator,
.awxjs__app__ui-panel-info-content .flight-status .indicator {
border: 2px solid #333;
border-radius: 15px;
height: 20px;
margin-top: -12px;
right: 0;
position: absolute;
top: 50%;
width: 20px;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
// Flight rule-to-color mapping dictionary
const colors = {
flightrule: {
VFR: '#1bbe08',
MVFR: '#1c56cf',
IFR: '#fa0708',
LIFR: '#fb06ff'
}
};
// Utility function that returns the color for a specific flight rule code
function flightRuleColor(code) {
code = (code || '').toUpperCase();
return colors.flightrule || '#999999';
}
window.onload = () => {
const aeris = new AerisWeather('CLIENT_ID', 'CLIENT_SECRET');
const utils = aeris.utils;
aeris.apps().then((apps) => {
const app = new apps.InteractiveMapApp('#app', {
map: {
strategy: 'leaflet',
zoom: 4,
layers: 'radar'
},
panels: {
layers: {
buttons: [{
id: 'radar',
value: 'radar:80',
title: 'Radar'
},{
id: 'satellite',
value: 'satellite:75',
title: 'Satellite'
},{
id: 'alerts',
value: 'alerts',
title: 'Alerts'
},{
id: 'airports',
value: 'airports',
title: 'Airports'
}]
},
info: {
sections: {
},
views: {
// Create an airport-specific view for the info panel
airport: {
// Return the network request to load the necessary data required by all of the views
request: () => {
const request = aeris.api();
const forecastFields = 'timestamp,tempF,icon,weatherPrimary,windSpeedMPH,windSpeedMinMPH,windSpeedMaxMPH,windGustMPH,snowIN,precipIN'.split(',').map((key) => 'periods.' + key);
request.addRequest(aeris.api().endpoint('forecasts').fields(forecastFields.join(',')).filter('3hr').limit(7));
request.addRequest(aeris.api().endpoint('convective/outlook').action('contains'));
request.addRequest(aeris.api().endpoint('lightning/summary').action('closest').radius('60mi').limit(100));
return request;
},
// Setup the array of views to display in the info panel for an airport.
// Each view object's `renderer` can be a string to use a built-in view, or a function that receives the data
// configured by the `data` function and returns the HTML to display for that view
views: [{
data: (data) => {
if (!data) return null;
data = data.observations || data;
return data.ob;
},
renderer: (data) => {
if (!data) return;
return (`
<div class="flightrule">
<div class="awxjs__ui-cols align-center">
<div class="awxjs__ui-expand">Flight Rule</div>
<div class="awxjs__ui-expand value">${data.flightRule} <div class="indicator" style="background:${flightRuleColor(data.flightRule)};"></div></div>
</div>
</div>
`);
}
},{
title: 'Impacts',
renderer: 'hazards'
},{
title: 'Observations',
renderer: 'obs'
},{
title: 'Short-Term Forecast',
renderer: 'forecast'
}]
}
}
}
}
});
// Add vector source showing airport flight rules as markers
const ids = 'id:AYPY;id:BIKF;id:BKPR;id:CYEG;id:CYHZ;id:CYOW;id:CYQB;id:CYUL;id:CYVR;id:CYWG;id:CYXU;id:CYYC;id:CYYJ;id:CYYT;id:CYYZ;id:DAAG;id:DGAA;id:DNAA;id:DNMM;id:DTTA;id:EBBR;id:EBLG;id:EDDB;id:EDDC;id:EDDF;id:EDDG;id:EDDH;id:EDDK;id:EDDL;id:EDDM;id:EDDN;id:EDDP;id:EDDS;id:EDDT;id:EDDV;id:EDDW;id:EDLW;id:EDSB;id:EETN;id:EFHK;id:EGAA;id:EGAC;id:EGBB;id:EGCC;id:EGCN;id:EGFF;id:EGGD;id:EGGP;id:EGGW;id:EGHH;id:EGHI;id:EGKK;id:EGLC;id:EGLL;id:EGNM;id:EGNT;id:EGNX;id:EGPD;id:EGPF;id:EGPH;id:EGSH;id:EGSS;id:EGTE;id:EGUL;id:EGUN;id:EGVA;id:EGVN;id:EHAM;id:EHBK;id:EHEH;id:EICK;id:EIDW;id:EINN;id:EKBI;id:EKCH;id:EKYT;id:ELLX;id:ENBO;id:ENBR;id:ENGM;id:ENTC;id:ENVA;id:ENZV;id:EPGD;id:EPKK;id:EPKT;id:EPMO;id:EPPO;id:EPWA;id:EPWR;id:ESGG;id:ESMS;id:ESPA;id:ESSA;id:ETAR;id:EVRA;id:EYVI;id:FACT;id:FAGG;id:FAJS;id:FALE;id:GCLP;id:GCTS;id:GCXO;id:GMMN;id:GOOY;id:HAAB;id:HECA;id:HEGN;id:HKJK;id:HKMO;id:HLLT;id:HSSS;id:KABQ;id:KADW;id:KAFW;id:KAGS;id:KAMA;id:KATL;id:KAUS;id:KAVL;id:KBAB;id:KBAD;id:KBDL;id:KBFI;id:KBGR;id:KBHM;id:KBIL;id:KBLV;id:KBMI;id:KBNA;id:KBOI;id:KBOS;id:KBTR;id:KBUF;id:KBWI;id:KCAE;id:KCBM;id:KCHA;id:KCHS;id:KCID;id:KCLE;id:KCLT;id:KCMH;id:KCOF;id:KCOS;id:KCPR;id:KCRP;id:KCRW;id:KCVG;id:KCVS;id:KDAL;id:KDAY;id:KDBQ;id:KDCA;id:KDEN;id:KDFW;id:KDLF;id:KDLH;id:KDOV;id:KDSM;id:KDTW;id:KDYS;id:KEDW;id:KEND;id:KERI;id:KEWR;id:KFFO;id:KFLL;id:KFSM;id:KFTW;id:KFWA;id:KGEG;id:KGPT;id:KGRB;id:KGSB;id:KGSO;id:KGSP;id:KGUS;id:KHIB;id:KHMN;id:KHOU;id:KHSV;id:KHTS;id:KIAD;id:KIAH;id:KICT;id:KIND;id:KJAN;id:KJAX;id:KJFK;id:KJLN;id:KLAS;id:KLAX;id:KLBB;id:KLCK;id:KLEX;id:KLFI;id:KLFT;id:KLGA;id:KLIT;id:KLTS;id:KLUF;id:KMBS;id:KMCF;id:KMCI;id:KMCO;id:KMDW;id:KMEM;id:KMGE;id:KMGM;id:KMHT;id:KMIA;id:KMKE;id:KMLI;id:KMLU;id:KMOB;id:KMSN;id:KMSP;id:KMSY;id:KMUO;id:KMYR;id:KOAK;id:KOKC;id:KONT;id:KORD;id:KORF;id:KPAM;id:KPBI;id:KPDX;id:KPHF;id:KPHL;id:KPHX;id:KPIA;id:KPIT;id:KPWM;id:KRDU;id:KRFD;id:KRIC;id:KRND;id:KRNO;id:KROA;id:KROC;id:KRST;id:KRSW;id:KSAN;id:KSAT;id:KSAV;id:KSBN;id:KSDF;id:KSEA;id:KSFB;id:KSFO;id:KSGF;id:KSHV;id:KSJC;id:KSKA;id:KSLC;id:KSMF;id:KSNA;id:KSPI;id:KSPS;id:KSRQ;id:KSSC;id:KSTL;id:KSUS;id:KSUU;id:KSUX;id:KSYR;id:KSZL;id:KTCM;id:KTIK;id:KTLH;id:KTOL;id:KTPA;id:KTRI;id:KTUL;id:KTUS;id:KTYS;id:KVBG;id:KVPS;id:KWRB;id:LATI;id:LBBG;id:LBPD;id:LBSF;id:LBWN;id:LCLK;id:LCPH;id:LDZA;id:LEAL;id:LEBL;id:LEMD;id:LEMG;id:LEPA;id:LEST;id:LFBD;id:LFBO;id:LFBV;id:LFML;id:LFMN;id:LFOA;id:LFOT;id:LFPG;id:LFPO;id:LGAV;id:LGIR;id:LGTS;id:LHBP;id:LIBD;id:LICC;id:LICJ;id:LIEE;id:LIMC;id:LIME;id:LIMF;id:LIMJ;id:LIML;id:LIMZ;id:LIPE;id:LIPH;id:LIPX;id:LIPZ;id:LIRA;id:LIRF;id:LIRN;id:LIRP;id:LJLJ;id:LKPR;id:LLBG;id:LLOV;id:LMML;id:LOWW;id:LPFR;id:LPLA;id:LPPD;id:LPPR;id:LPPT;id:LQSA;id:LROP;id:LSGG;id:LSZH;id:LTAC;id:LTAF;id:LTAI;id:LTAJ;id:LTAZ;id:LTBA;id:LTBJ;id:LTBS;id:LTCE;id:LTCG;id:LTFC;id:LTFE;id:LTFJ;id:LXGB;id:LYBE;id:LYPG;id:LZIB;id:MDPC;id:MDSD;id:MKJP;id:MMAA;id:MMGL;id:MMHO;id:MMMX;id:MMMY;id:MMPR;id:MMSD;id:MMTJ;id:MMUN;id:MPTO;id:MUHA;id:MUVR;id:MWCR;id:MYNN;id:MZBZ;id:NCRG;id:NZAA;id:NZCH;id:NZWN;id:OBBI;id:OEDF;id:OEDR;id:OEJN;id:OEMA;id:OERK;id:OIIE;id:OIII;id:OIMM;id:OISS;id:OITT;id:OJAI;id:OKBK;id:OLBA;id:OMAA;id:OMDB;id:OMDW;id:OMSJ;id:OOMS;id:OPRN;id:OPST;id:ORBI;id:ORMM;id:OSAP;id:OSDI;id:OSLK;id:OTBD;id:PAFA;id:PANC;id:PGUM;id:PHNL;id:PR20;id:RCBS;id:RCKH;id:RCTP;id:RJAA;id:RJBB;id:RJCC;id:RJFF;id:RJFK;id:RJGG;id:RJNS;id:RJOO;id:RJTT;id:RJTY;id:RKJB;id:RKJK;id:RKPC;id:RKPK;id:RKSI;id:RKSO;id:RKSS;id:RKTU;id:ROAH;id:RODN;id:RPLC;id:RPLL;id:RPMD;id:RPVM;id:SAEZ;id:SBBE;id:SBBR;id:SBCF;id:SBCT;id:SBFL;id:SBGL;id:SBGR;id:SBSP;id:SBSV;id:SCEL;id:SELT;id:SEQM;id:SKBO;id:SPIM;id:SPZO;id:SUMU;id:TJSJ;id:TMJG;id:TNCM;id:UAAA;id:UACC;id:UAFM;id:UAKK;id:UBBB;id:UDYZ;id:UGTB;id:UKBB;id:UKCC;id:UKFF;id:UKHH;id:UKOO;id:ULLI;id:UMMS;id:UNKL;id:URSS;id:USSS;id:UTTT;id:UUBW;id:UUDD;id:UUEE;id:UUMU;id:UWUU;id:UWWW;id:VABB;id:VAGO;id:VCBI;id:VDPP;id:VDSR;id:VECC;id:VHHH;id:VIAR;id:VIDP;id:VISM;id:VMMC;id:VOBL;id:VOCI;id:VOCL;id:VOHS;id:VOMM;id:VOPN;id:VOTV;id:VRMM;id:VTBD;id:VTBS;id:VTCC;id:VVDN;id:VVNB;id:VVTS;id:VYMD;id:VYYY;id:WAAA;id:WADD;id:WARR;id:WBSB;id:WIII;id:WMKK;id:WSSS;id:YBBN;id:YMML;id:YPPH;id:YSCB;id:YSSY;id:ZBAA;id:ZBNY;id:ZBTJ;id:ZBYN;id:ZGGG;id:ZGHA;id:ZGKL;id:ZGNN;id:ZGSZ;id:ZHCC;id:ZHHH;id:ZJHK;id:ZJSY;id:ZLXY;id:ZPPP;id:ZSAM;id:ZSFZ;id:ZSHC;id:ZSJN;id:ZSNB;id:ZSNJ;id:ZSPD;id:ZSSS;id:ZSWZ;id:ZUCK;id:ZUGY;id:ZUUU;id:ZWWW;id:ZYHB;id:ZYTL;id:ZYTX';
const airportsRequest = aeris.api()
.endpoint('observations')
.action('search')
.query(ids)
.fields('id,loc,ob,place')
.from('-2hours')
.limit(1000);
let airportData;
const airports = app.addSource('airports', 'vector', {
refresh: 60,
data: {
service: airportsRequest
},
style: {
marker: (data) => {
return {
svg: {
shape: {
type: 'circle',
fill: {
color: flightRuleColor(utils.get(data, 'ob.flightRule'))
},
stroke: {
color: '#ffffff',
width: 2
}
}
},
size: [16, 16]
};
}
}
});
airports.on('data:load', (e) => {
if (e.data) {
airportData = (e.data.results || []).reduce((prev, current) => {
prev[current.id] = current;
return prev;
}, {});
}
});
// Show the info panel with the 'airport' content view when an airport marker
// is selected on the map
app.map.on('marker:click', (e) => {
const data = e.data.data || {};
// Get the source identifier (key) that triggered the click event and only show the
// info panel if the source came from our custom airport data source
const source = data.awxjs_source;
if (source === 'airports') {
const id = data.id;
const place = data.place || {};
const name = utils.strings.toName(place.name);
// Show the info panel and then trigger the panel's content request while
// also passing in the cached observations for the airport to use in the content view
app.showInfo('airport', `${id} - ${name}`).load({ p: id }, { observations: airportData[id] });
}
});
});
};
</script>
</body>
</html>