WorldWindJS Example

WorldWindJS geo-browser example using Bootstrap and Knockout

by Bruce Schubert

HTML

<!doctype html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="WorldWindJS Example">
    <meta name="author" content="Bruce Schubert">
    <link rel="icon" href="images/nasa.gov.ico">

    <title>WorldWindJS Example</title>

    <!-- Bootstrap 4.0 CSS compiled and minified -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- Font Awesome icons (see: https://fontawesome.com/icons?d=gallery) -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css" integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous">

    <!-- Custom styles and overrides -->
    <link href="custom.css" rel="stylesheet">
  </head>

  <body>
    <!--Main Menu-->
    <nav class="navbar navbar-expand-md fixed-top navbar-dark bg-dark">
      <!--Branding icon and text-->
      <a class="navbar-brand" href="https://github.com/emxsys/worldwind-web-app">
                <img src="images/nasa-logo_32.png" width="32" height="32" class="d-inline-block align-top" alt="">
                WorldWindJS
            </a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#main-menu" aria-controls="main-menu" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>

      <!--Main menu content-->
      <div class="collapse navbar-collapse" id="main-menu">
        <ul class="navbar-nav mr-auto">
          <!--Layers-->
          <li class="nav-item">
            <a class="nav-link" data-toggle="collapse" href="#layers" role="button">
                            <span class="fas fa-list"...

CSS

body {
  /*Account for the navbar*/
  padding-top: 3.5rem;
}

.globe {
  width: 100%;
  height: calc(100vh - 3.5rem);
  background-color: black;
}

.globe-overlay {
  position: absolute;
  top: 3.5rem;
}

.globe-card {
  background: rgba(255, 255, 255, 0.7);
}

.globe-button {
  background: rgba(255, 255, 255, 0.25);
}

.globe-dropdown {
  background: rgba(255, 255, 255, 0.25);
}

/*When the modal fills the screen it has no margin on top and bottom*/

/*Centers the modal*/

.modal-dialog {
  margin: 0 auto;
}

/*Sets the maximum height of the entire modal to 100% of the screen height*/

.modal-content {
  max-height: 100vh;
}

/*Sets the maximum height of the modal body to 90% of the screen height*/

.modal-body {
  max-height: 90vh;
}

/*Sets the height of a modal's canvas to 30% or 200px*/

.modal-body-canvas {
  max-height: 200px;
  height: 30vh;
}

/*Sets the height of a modal's canvas to 60% minus the height of a footer*/

.modal-body-table {
  max-height: calc(60vh - 71px);
  overflow-y: auto;
}

/*Extra small devices (portrait phones, less than 576px)*/

@media (max-width: 575.98px) {
  .card-columns {
    column-count: 1;
  }
}

/*Small devices (landscape phones, 576px and up)*/

@media (min-width: 576px) and (max-width: 767.98px) {
  .card-columns {
    column-count: 1;
  }
}

/*Medium devices (tablets, 768px and up)*/

@media (min-width: 768px) and (max-width: 991.98px) {
  .card-columns {
    column-count: 2;
  }
}

/*Large devices (desktops, 992px and up)*/

@media (min-width: 992px) and (max-width: 1199.98px) {
  .card-columns {
    column-count: 3;
  }
}

/*Extra large devices (large desktops, 1200px and up)*/

@media (min-width: 1200px) {
  .card-columns {
    column-count: 4;
  }
}

.noninteractive {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  pointer-events: none;
}

.interactive {
  -webkit-touch-callout: auto !important;
 ...

JavaScript

/* global WorldWind */

$(document).ready(function() {
  "use strict";
  // Set your Bing Maps key which is used when requesting Bing Maps resources.
  // Without your own key you will be using a limited WorldWind developer's key.
  // See: https://www.bingmapsportal.com/ to register for your own key and then enter it below:
  const BING_API_KEY = "";
  if (BING_API_KEY) {
    // Initialize WorldWind properties before creating the first WorldWindow
    WorldWind.BingMapsKey = BING_API_KEY;
  } else {
    console.error("app.js: A Bing API key is required to use the Bing maps in production. Get your API key at https://www.bingmapsportal.com/");
  }

  // Set the MapQuest API key used for the Nominatim service.
  // Get your own key at https://developer.mapquest.com/
  // Without your own key you will be using a limited WorldWind developer's key.
  const MAPQUEST_API_KEY = "";


  /**
   * The Globe encapulates the WorldWindow object (wwd) and provides application
   * specific logic for interacting with layers.
   * @param {String} canvasId
   * @param {String|null} projectionName
   * @returns {Globe}
   */
  class Globe {
    constructor(canvasId, projectionName) {
      // Create a WorldWindow globe on the specified HTML5 canvas
      this.wwd = new WorldWind.WorldWindow(canvasId);

      // Projection support
      this.roundGlobe = this.wwd.globe;
      this.flatGlobe = null;
      if (projectionName) {
        this.changeProjection(projectionName);
      }

      // A map of category and 'observable' timestamp pairs
      this.categoryTimestamps = new Map();

      // Add a BMNGOneImageLayer background layer. We're overriding the default 
      // minimum altitude of the BMNGOneImageLayer so this layer always available.
      this.addLayer(new WorldWind.BMNGOneImageLayer(), {
        category: "background",
        minActiveAltitude: 0
      });

    }

    get projectionNames() {
      return [
        "3D",
        "Equirectangular",
        "Mercator",
   ...