Published on GEOG 585: Web Mapping (https://www.e-education.psu.edu/geog585)

Home > Lessons > Lesson 8: Going beyond "dots on a map"

Lesson 8: Going beyond "dots on a map"

The links below provide an outline of the material for this lesson. Be sure to carefully read through the entire lesson befor returning to Canvas to submit your assignments.
Note: You can print the entire lesson by clicking on the "Print" link above.

Overview

Note: Currently this lesson teaches the Leaflet API. If you are looking for the previous materials on OpenLayers, see the Lesson 8 archive page [1].

So far, you've learned how to create various types of web map layers and overlay them using Leaflet. Simple mashups with "dots on a map" and a few popups may be all you need in many situations. In fact, some managers are thrilled to see these basic web maps if they have never visualized their data in geographic form before; however, as a student of GIS you'll want to be aware of additional possibilities to make the map layers more interactive or informative.

This lesson introduces you to a variety of "extra" things you can do to enrich the interactivity and information exposed by your web maps. Some of these features cannot be covered in full detail in this course; however, the concepts are introduced here in case you ever encounter them or need to use them in the workplace. During the lesson assignment, you'll have the opportunity to choose one of these techniques (or other FOSS of your choice) and explore it more fully.

A word of caution is necessary here: Just because you can do something doesn't mean that you should. Many of the best web maps are effective because they are focused and uncluttered. You'll see in this lesson that Leaflet makes it easy to add all kinds of controls to your web maps, but before doing this, pause and determine whether the extra features are really needed. The same applies to WFS, WPS, widgets provided by web display frameworks, and other features mentioned in this lesson.

Objectives

  • Implement techniques for data filtering and classification using Leaflet.
  • Describe OGC specifications for vector data editing (WFS) and geoprocessing (WPS) in web maps.
  • Implement the layer switcher and other controls from Leaflet according to application needs.
  • Choose a web presentation framework such as Bootstrap and apply it to your Leaflet applications.
  • Query and display geographic attributes (including links to images and web pages) in your web map application.
  • Learn and critically evaluate a new piece of FOSS GIS.

Checklist

  • Read the Lesson 8 materials on this page.
  • Complete the walkthrough.
  • Complete the Lesson 8 assignment.

Symbolizing layers based on attribute values

Choropleth maps, proportional symbols maps, and on-the-fly feature filtering all provide alternative functionality to the typical "dots on a map" mashup. How can you achieve these things with Leaflet? In this section of the lesson, we'll talk about how to create Leaflet symbols based on attributes in your data. You can then apply these symbols to vectors to get various types of maps, such as choropleth maps based on class breaks.

First, it's important to be clear that symbolizing layers on the client using Leaflet code is not the only way to achieve choropleth maps, proportional symbols, etc., on the web. Earlier in this course, you became familiar with dynamically drawn WMS maps defined by SLD styling, and tiled maps defined by CartoCSS. In these approaches, you do the cartography on the server rather than the client. Server-side maps are most appropriate for advanced cartographic effects. They may also result in better performance when hundreds or thousands of features are in play.

Why, then, are we discussing making our specialized maps with Leaflet styles when so many other cartographic tools lie within reach? The reason is that defining symbols on the client opens the door to flexibility and interactivity.

  • The flexibility comes because the data is separated from the styling; thus, you don't have to set up multiple web services in order to get multiple types of styling in your apps. Each app can define different styling rules in its client side code while utilizing the same source data.
  • The interactivity comes from the ability to do re-classification and symbolization on the fly by invoking client-side code functions. For example, you could allow the user to switch from an equal interval classification to a quantile classification, or adjust the maximum size of a proportional symbol, all without making a return trip to the server or setting up additional web services.

Reading attributes in Leaflet

In order to create a styling rule based on some attribute value, it's first necessary to read the attribute. How do you do this in Leaflet?

Let's consider a dataset that I downloaded from the Buenos Aires open data portal [2] showing subway ("subte") lines. Each subway line is composed of many polyline segments. Each polyline segment has an attribute stating which subway line it belongs to (e.g., "LINEA B"). I've created a GeoJSON file out of this and I want to display it in a web map using unique colors for each subway line, like this:

 Subway map with unique colors for each line
Figure 8.1

First, let's take the easy case where the color value is directly coded into the attribute table. Notice the COLOR field below which contains color hex values for each line segment:

 Subway attribute table
Figure 8.2

If you are fortunate enough to have this setup and the colors work for you, then you can apply them directly to the color property when you define the layer style. In Leaflet you can use the syntax feature.properties.<PROPERTY> to get the value of any feature attribute:

// Set up styles for subway lines
function subteStyle(feature) {
  return {
    "color": feature.properties.COLOR,
    "weight": 5
  };
}          
// Create layer and add it to the map
var subteLayer = L.geoJSON(subteData, {
  style: subteStyle
});

The above code creates a vector layer from the variable subteData which comes from a GeoJSON file. To style the layer, the sybteStyle function reads the hex value from the COLOR field and inserts it in the color property of the layer. Notice how the syntax feature.properties.COLOR is utilized to read the color value.

Symbols for unique nominal attributes

Although the above technique is convenient, it's not always practical. What if the colors provided in the file aren't appropriate for your map? Fortunately, in our scenario, you could work around this by providing color values in your JavaScript (i.e., your client-side) code based on the name of the subway line.

Let's look at how you'd apply some style rules on the client side using Leaflet. Examine the following variation on the code we viewed previously. This code produces the exact same map:

// Set up styles for subway lines
function subteStyle(feature) {
  var colorToUse;
  var line = feature.properties.LINEASUB;
            
  if (line === "LINEA A") colorToUse = "#46C7FA";
  else if (line === "LINEA B") colorToUse = "#E81D1A";
  else if (line === "LINEA C") colorToUse = "#4161BA";
  else if (line === "LINEA D") colorToUse = "#599C65";
  else if (line === "LINEA E") colorToUse = "#65018A";
  else if (line === "LINEA H") colorToUse = "#FAF739";
  else colorToUse = "#000000";
            
  return {
    "color": colorToUse,
    "weight": 5
  };
}
          
// Create layer and add it to the map
var subteLayer = L.geoJSON(subteData, {
  style: subteStyle
});

The above example employs a function subteStyle to read the attribute LINEASUB from each feature, thereby figuring out the name of the subway line (LINEA A, LINEA B, etc.). If/then logic is applied to find the appropriate color to use based on the subway line name. Finally, this color is applied to a style returned by the function.

Proportional symbols based on numeric attributes

If you have a field with some numeric attribute, such as the length of a subway line or the number of riders per day, you may want to size the symbols proportionally so that big values are represented with a bigger symbol. Let's consider an example where we have a GeoJSON dataset showing statistics for some of South America's largest metro rail systems. You'll get more familiar with this dataset in the lesson walkthrough, but here's the basic attribute table and map:

 Table of metro systems
Figure 8.3
 Map of metro systems with basic symbols
Figure 8.4

In the above image, the metro systems are all drawn using the same symbol. But let's suppose that you wanted to proportionally size the symbols so that the more stations in a metro system, the larger the map symbol. Notice the STATIONS attribute contains this information. The desired map would look something like the following:

 Metro systems with proportional symbols
Figure 8.5

Accomplishing the proportional symbols in Leaflet requires you to define some mathematical function that will size each symbol based on each feature's attribute value for STATIONS.  The syntax below is somewhat advanced, but pay attention to the line which reads the value for STATIONS, divides it by 80, and multiplies it by 30 to derive the width and height in pixels for any given metro system symbol. These numbers signify that a metro system with 80 stations will have a symbol 30 pixels wide, a metro system with fewer stations will be less than 30 pixels, and a metro system with more stations will be greater than 30 pixels (the numbers 80 and 30, of course, are entirely arbitrary and could be adjusted to fit your own data):

// function to size each icon proportionally based on number of stations
function iconByStations(feature){
  var calculatedSize = (feature.properties.STATIONS / 80) * 30;
            
  // create metro icons
  return L.icon({
    iconUrl: 'metro.svg',
    iconSize: [calculatedSize, calculatedSize]
  });
}
        
// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {icon: iconByStations(feature)});
  }
});

In the above code, iconSize is a two-item JavaScript array containing the width and the height in pixels that should be applied to the icon. Also, notice the use of the pointToLayer property, which is necessary when you want to replace the default Leaflet markers with your own graphics.

Symbols for data classes based on numeric attributes

In some situations, it may be more appropriate to break up your numerical data into various classifications that are symbolized by graduated colors or some similar approach. This is especially true for line and polygon datasets that are sometimes harder to conceptualize using proportional symbols. The boundaries for your classes (in Esri software you may have heard these referred to as "class breaks") could be determined based on equal interval, quantile, natural breaks, or some other arbitrary scheme.

For example, in the image below, metro systems with over 100 stations are symbolized using dark red. Systems with 50 to 100 stations are symbolized with red. Systems with fewer than 50 stations are symbolized with pink:

 Metro systems with colorized classifications
Figure 8.6

To symbolize data classes in Leaflet, we'll read one of the feature attributes, then use if/then logic to check it against our class breaks. The code below defines the three classes used in our metro rail example. Each class references a different SVG symbol with its unique hue of red:

// create metro icons
var metroLowIcon = L.icon({
  iconUrl: 'metro_low.svg',
  iconSize: [25,25]
});

var metroMediumIcon = L.icon({
  iconUrl: 'metro_medium.svg',
  iconSize: [25,25]
});

var metroHighIcon = L.icon({
  iconUrl: 'metro_high.svg',
  iconSize: [25,25]
});
          
// function to use different icons based on number of stations
function iconByStations(feature){
  var icon;
  if (feature.properties.STATIONS >= 100) icon = metroHighIcon;
  else if (feature.properties.STATIONS >= 50) icon = metroMediumIcon;
  else icon = metroLowIcon;

  return icon;
}
        
// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {icon: iconByStations(feature)});
  }
});

Although the above may look like a lot of code, notice that half of it is just setting up the icons. A function that classified lines or polygons might be much simpler because a single style could be defined with a varying stroke or fill color based on the attribute of interest.

If you intend to use a classification system such as Jenks natural breaks, equal interval, quantile, etc., you must calculate the break values yourself (or find a library [3] that does it) before defining the rules. You can either do this by hand or add more JavaScript code to calculate the values on the fly.

Ways to limit feature visibility based on attributes

In some situations, you may want to display only a subset of the features in a dataset, based on some attribute value or combination of values. (If you're familiar with Esri ArcMap, this is called a "definition query".) Suppose you wanted to show only the metro systems whose COUNTRY attribute was coded as "Brazil":

 Metro systems filtered by COUNTRY to show Brazil only
Figure 8.7

You can do this directly in the filter property when you create the layer in Leaflet. Here you write a function that evaluates a feature and returns a value of true or false. Leaflet will only draw features for which a value of true is returned:

// create metro icons
var metroIcon = L.icon({
  iconUrl: 'metro.svg',
  iconSize: [25,25]
});

        
// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {
      icon: metroIcon    
    });
  },
  filter: function(feature, layer) {
    if (feature.properties.COUNTRY === "Brazil") return true;
    else return false;
  }
});

The filter function in the above example tests for features where the COUNTRY property equals "Brazil". Note that the above example simply reads an attribute, it does not do a spatial query to find the bounds of Brazil. Your website will run much faster if you can preprocess the data to put a country attribute on each metro system, rather than trying to figure that out on the fly using spatial processing in a web environment.

Now, let's look at a scenario with a numeric attribute. Suppose we wanted to show only the metro systems with over 75 stations:

 Metro systems filtered to those with over 75 stations
Figure 8.8

This could be accomplished with the following filter:

// create metro icons
var metroIcon = L.icon({
  iconUrl: 'metro.svg',
  iconSize: [25,25]
});

        
// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {
      icon: metroIcon    
    });
  },
  filter: function(feature, layer) {
    if (feature.properties.STATIONS > 75) return true;
    else return false;
  }
});

In the code above, note the query for a STATIONS value that is greater than 75.

Conclusion

Leaflet offers a variety of options for symbolizing data on the client side based on attribute values. You should not feel limited to using the default symbology or a single symbol type for the entire layer.

Creating Leaflet styles can require some trial and error, along with some skillful debugging. You'll have more success if you start with a working example and tweak it piece by piece, frequently stopping to test your alterations. This section of the lesson is deliberately code-heavy so that you will have many examples available to get you started.

Spicing up your mapping site with controls

Although Leaflet does not offer a point-and-click interface for building a web application, it does give you a few pre-coded "controls" that you can add to the map with just a few lines of code. These include a scale bar, layer switcher, zoom buttons, and attribution box. OpenLayers offers these same types of controls, plus a few more such as an overview map, geographic coordinates written on the page, and so on.

The example below shows a Leaflet layer switcher control in action. Hovering the mouse over (or tapping) the layer icon in the corner displays a layer list where you can choose a basemap and toggle the visibility of thematic layers. This image shows the default OpenStreetMap Mapnik basemap selected, with the option to switch to the Stamen Toner basemap. You'll apply this code in your walkthrough later in the lesson. Only one thematic layer is available below, the "Subway lines" layer; however, you could include more layers here simply by adding them to the Leaflet map.

Layer switcher control

Figure 8.9

Here's how you would code the layer switcher above. First, set up all the layers and add the ones to the map that you want to have initially visible. Then, call a function that sets up two simple JavaScript objects containing your basemaps and thematic layers. Finally, create the layer switcher (L.control.layers [4]), passing in those two JavaScript objects as parameters.

          // create and add osm tile layer
          var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
          });
          osm.addTo(map);
          
          // create stamen osm layer (not adding it to map)
          var stamen = L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '&copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, under an <a href="http://www.openstreetmap.org/copyright" title="ODbL">open license</a>. Toner style by <a href="http://stamen.com">Stamen Design</a>'
          });
          
          // Set up styles for subway lines
          function subteStyle(feature) {
            return {
               "color": feature.properties.COLOR,
                "weight": 5
            };
          }
      
          // Create layer and add it to the map
          var subteLayer = L.geoJSON(subteData, {
            style: subteStyle
          });
          
          subteLayer.addTo(map);
          
          createLayerSwitcher();
          
          // function to create a layer switcher control
          function createLayerSwitcher() {
            // define basemap and thematic layers and add layer switcher control
           var basemaps = {
              "OSM Mapnik": osm,
              "OSM Stamen Toner": stamen
            };

            var overlays = {
              "Subway lines": subteLayer
            };
            L.control.layers(basemaps,overlays).addTo(map);
          }
      

Usually control frameworks are extensible so you may even decide to pull in controls developed by others or, if you're feeling ambitious, code your own. Although customizing the out-of-the-box Leaflet controls is beyond the scope of this course, you should be aware that controls have a stylesheet you can tweak. Also, controls with images can be customized by substituting your own image. Examining the Leaflet stylesheets and example stylesheets may help you understand which CSS properties to override when applying your customizations.

The Leaflet API reference describes the controls here [5] in the namespace L.Control. The best way to learn about controls is to experiment with them yourself and expand your functionality, bit by bit, using existing examples, documentation, and some trial and error. In other web mapping APIs, controls may be called "widgets," "plugins," or other names.

 

WFS and editing vector data on the web

In Lesson 7, you saw some ways that vector data can be drawn by the web browser or client. The lesson focused on standalone KML and GeoJSON files, yet it is also possible for a web service to send the data to the client on request. The data can be in any format as long as both the server and the client are following the same specification. To standardize the process of sending vector data through web services, the Open Geospatial Consortium (OGC) has produced the Web Feature Service (WFS) specification.

You've already seen a related service, the WMS in previous lessons. How is WMS different from WFS? WMS involves the server sending a single map image, whereas WFS involves the server sending vector data as text to be drawn by the client. In simple terms, a WMS map is drawn by the server and a WFS map is drawn by the client.

WFS request and response formats

Like a WMS, a WFS supports a set of operations that typically take in parameters directly within the URL. These operations include GetCapabilities, DescribeFeatureType, and GetFeature. The GetFeature operation is the one that actually retrieves features.

Below is an example GetFeatures WFS request for the US state of Colorado (I picked something with a simple geometry). I adapted this from the Boundless WFS tutorial [6], which I highly recommend reading. See if you can guess what each parameters signifies, then click the link and view the response.

http://suite.opengeo.org/geoserver/wfs?service=wfs&version=1.1.0&request=GetFeature&typename=usa:states&featureid=states.23 [7]

By examining the above URL parameters, you can see that a feature is requested from the WFS using version 1.1.0 of the WFS specification. The service is being hosted on GeoServer in a layer named states in the USA workspace. The feature with index 23 is returned.

A WFS returns data using Geography Markup Language (GML), a specification for expressing GIS data using XML. GML can contain both geometry and attribute information. Because it is based on XML and is designed to be flexible enough to handle many geographic feature types, GML is much more verbose (takes more text) than GeoJSON. Deep down in the GML for Colorado, you can find the geometry:

<gml:posList>37.48468400000007 -109.04348799999995 38.164690000000064 -109.04176199999989 38.27548899999999 -109.06006199999996 41.0006590000001 -109.05007599999999 41.00235900000007 -102.051717 36.993015999999955 -102.04208899999992 36.99908400000004 -109.0452229999999 37.48468400000007 -109.04348799999995</gml:posList>

You can also find attributes like this:

<usa:NAME10>Colorado</usa:NAME10>

The same type of request could be made to one of your own services running on GeoServer. Here's how I made a request for a Philadelphia neighborhood using one of the layers we published earlier in this course:

http://localhost:8080/geoserver/wfs?service=wfs&version=1.1.0&request=GetFeature&typename=geog585:Neighborhoods&featureid=Neighborhoods.12

The response looks like this and contains geometry and attributes for the Olney neighborhood:

<wfs:FeatureCollection numberOfFeatures="1" timeStamp="2014-03-03T15:07:31.822Z" xsi:schemaLocation="http://localhost:8080/geoserver/geog585 http://localhost:8080/geoserver/wfs?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=geog585%3ANeighborhoods http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd"><gml:featureMembers><geog585:Neighborhoods gml:id="Neighborhoods.12"><geog585:the_geom><gml:MultiSurface srsDimension="2" srsName="urn:x-ogc:def:crs:EPSG:3857"><gml:surfaceMember><gml:Polygon srsDimension="2"><gml:exterior><gml:LinearRing srsDimension="2"><gml:posList>-8363968.786751106 4869301.13520122 -8363706.077778376 4871057.31164155 -8363880.846283749 4871132.918517317 -8363697.377540309 4872031.511981935 -8363780.660729433 4872179.806916264 -8363847.448310932 4872208.890548547 -8363802.926044645 4872557.878939522 -8363802.44449278 4872626.491915396 -8363025.915000884 4872530.247301338 -8361543.138729884 4872310.6731403675 -8361453.88028348 4872223.294811407 -8361493.045963939 4872015.489274301 -8361627.94355705 4871826.7318475135 -8361690.687270048 4871673.398417745 -8361627.94355705 4871403.748827802 -8361286.901117077 4870791.777211798 -8361326.368936536 4870458.7113405885 -8361498.408149585 4869986.8871721085 -8361555.111808623 4869831.380121785 -8362695.297708079 4869623.850560427 -8363168.406381819 4869548.2551895585 -8363968.786751106 4869301.13520122</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></geog585:the_geom><geog585:STATE>PA</geog585:STATE><geog585:COUNTY>Philadelphia</geog585:COUNTY><geog585:CITY>Philadelphia</geog585:CITY><geog585:NAME>Olney</geog585:NAME><geog585:REGIONID>214146.0</geog585:REGIONID></geog585:Neighborhoods></gml:featureMembers></wfs:FeatureCollection>

WFS servers and clients

Although the syntax for WFS requests and responses may look intimidating, you will rarely have to worry about composing this yourself. Various FOSS and proprietary software packages include support for viewing and serving WFS.

WFS servers

As shown in the above examples, GeoServer can expose your layers through WFS. This is enabled by default and requires no configuration on your part. Other FOSS GIS servers such as Map Server and Deegree support the creation of WFS web services.

In the proprietary realm, Esri ArcGIS for Server gives users the option to expose their web services through WFS; however, Esri has developed its own "feature service" that works through REST and accomplishes many of the same things as a WFS. Be aware that the Esri web mapping APIs and editing widgets are designed to work with the feature service rather than WFS. The communication patterns of the feature service are openly documented in the GeoServices REST Specification.

WFS clients

Support for WFS varies across web mapping APIs. WFS can be viewed "out of the box" as a vector layer in OpenLayers, but not within Leaflet. As mentioned above, Esri web mapping APIs are designed to work with the REST feature service rather than WFS.

Although Leaflet won't draw the GML responses sent by a WFS, it will draw GeoJSON; therefore, one pattern of working with WFS via Leaflet is to configure the server to send back GeoJSON instead of GML. You can then send WFS requests to the server via AJAX (asynchronous web requests) and use Leaflet classes to draw the geoJSON coming back. Such patterns are exposed through numerous forum posts like this one [8], as well as third party plugins.

On the desktop side, QGIS supports viewing and editing WFS. (Note: I got errors when trying to edit a WFS in QGIS, but I did not have a database or proxy host behind the layer.) Other FOSS clients such as uDig also recognize the WFS format. In Esri ArcMap, you use the Data Interoperability Extension to add WFS layers to the map (see "Connecting to a WFS Service [9]").

Transactional WFS (WFS-T) and web-based editing

The WFS specification also defines rules for feature editing, opening the door to building clients that can edit geographic data over the web. A WFS enabled for editing is known as a transactional WFS, or WFS-T. Beyond the standard WFS operations such as GetFeature, WFS-T supports an additional operation called Transaction, and may also support operations for locking features to prevent concurrent edits.

Creating web editing applications requires both a server that can handle the incoming transactions and a client that provides visual support for the edit sketching, vertex manipulation, attribute entrance into text boxes, and so forth. The client sketching operations must be wired up to the WFS-T requests. Unless you're ready to write a lot of JavaScript, it's best to start with existing samples, widgets, or web controls. Leaflet offers a Draw [10] control that can take care of the sketching part. See a full example here [11] of what it can do. The respec/leaflet.wfs-t [12] is an example of a plugin designed to wire up the Draw control to WFS-T requests. (Full disclosure: I have not tested this particular plugin, but there are other plugins based on the same concept).

Leaflet Draw control example
Figure 8.11

OpenLayers also offers some basic buttons for sketching, modifying, deleting, and saving features. You can see them in action in this developer example [13].

Before you expose any dataset for editing over the web, you should carefully think through your quality control and data storage architectures. For example, perhaps you want to expose a copy of your database for web editing, then have an analyst review any changes before they are pushed to your master GIS database. If in-house editors are making changes to the master database, you'll also need a way to periodically push those to your web copy. In other cases, you may check incoming edits with a script to make sure they comply with certain topology rules or attribute schema requirements.

WPS, Turf.js, and spatial data processing on the web

Both FOSS and proprietary GIS software offer spatial data processing functions such as buffer, union, contour, interpolate, and so on. You invoked some of these using QGIS and GDAL in earlier lessons. But what if you wanted to allow people to run these functions from a web browser? For example, suppose you wanted to allow users of your web map to draw a polygon and then see a calculation of the total population and number of health clinics within that polygon. You'd be able to expose the GIS to many people while only having to install GIS software and perform the spatial data processing on one machine: the server.

OGC has released a specification for invoking spatial data processing through web services. It's called the Web Processing Service (WPS) specification. Like the other OGC services you've learned about, it offers a set list of operations you can call. These are: GetCapabilities, DescribeProcess, and Execute. Of course, the Execute operation is the one that launches the request to actually perform the processing. The server's response contains the result. GML may be used to transfer information about vector features in either the request or the response.

As you are probably aware from running GDAL and ArcToolbox tools, spatial data processing functions can sometimes require many input parameters. For example, a buffer process might require you to specify the width of the buffer, whether it will be applied to both sides of the feature, whether the ends should be capped or rounded, etc. Each tool has its own set of parameters and syntax for describing them. Because the inputs can be so long and complex (especially if geometry is included), sometimes you can put the inputs in their own XML file and POST them to the server, rather than putting all the parameters into the URL as a GET request like you have seen with WMS and WFS in previous lessons. Some servers and browsers impost limits on the length of an HTTP GET request, whereas HTTP POST requests can typically be much longer.

The WPS spec itself doesn't say which types of spatial data processing operations a WPS must support; that's up to whoever builds and administers the service. There are hundreds of potential operations that can be included. When you first use a WPS, you can invoke the GetCapabilities operation to find out which processes are available.

WPS servers

GeoServer offers a WPS extension [14] that exposes a set of spatial processes from the FOSS JTS Topology Suite [15], as well as some other processes developed specifically by GeoServer. We will not install this extension in this course, but I encourage you to browse through the documentation if you think that you may use it in your workplace or other academic work.

The Zoo Open WPS Platform [16] and PyWPS [17] are other examples of FOSS WPS implementations. In the proprietary realm, Esri ArcGIS Server can serve a WPS from a ModelBuilder model that you create from ArcToolbox tools or scripts.

WPS clients

A few GUI-based WPS clients are available that allow you to select tools and supply their parameters in text boxes or dropdown lists. QGIS has a WPS plugin [18] that works this way, allowing you to call a WPS from the desktop.

When it comes to invoking a WPS directly from a web application, some web map APIs offer helper classes or libraries that can help you. Leaflet is not one of these (I will get to that in a minute). OpenLayers supports WPS through the use of OpenLayers.WPSClient [19]. When you instantiate this object, you supply the URL of the WPS server. You then set up JavaScript objects containing all the parameters of the process you want to invoke. Finally, you execute the process and specify what should be done with the results. See this OpenLayers developer example available online [20].

Even when you use a WPS client plugin, library, or object, you still need to be familiar with the process and its documentation, so that you can supply the correct syntax for the parameters. One mistake in syntax can derail the entire processing operation. Furthermore, WPS servers and clients are often either in the early stages or maturity or are designed for power users who are comfortable with a lack of a GUI and extensive documentation.

Turf.js as a WPS alternative

Many spatial processing operations are rather common and there is a great desire from GIS web developers to invoke these without the overhead of a WPS. The Turf.js [21] library has gained popularity for allowing geoprocessing directly on GeoJSON vectors in a JavaScript environment. This API was developed at Mapbox but is free to use and modify under the open MIT license [22]. Operations available through Turf.js include dissolving, unioning, intersecting, buffering, aggregating, calculating centroids, and so forth.

You can download Turf.js to your own machine or access it online via a CDN at: https://npmcdn.com/@turf/turf/turf.min.js [23]. Because Turf.js works on GeoJSON, it fits very nicely with Leaflet. Most of the abundant Turf.js developer examples are shown using Mapbox's API (for obvious reasons), but the way you would invoke them from Leaflet is essentially the same. For example, the following snippet is from the Turf.js analysis walkthrough [24]:

  // Using Turf, find the nearest hospital to library clicked
  var nearestHospital = turf.nearest(e.layer.feature, hospitalFeatures);

In this situation, both parameters of the turf.nearest() function are GeoJSON objects: one representing a library building, and another one representing a set of hospital buildings. The function finds the hospital nearest the clicked library and returns it as GeoJSON. Fundamentally, it doesn't matter which API you use to display the output GeoJSON.

Performance considerations

Before implementing any kind of spatial data processing on the web, consider ways that you might preprocess the data in order to eliminate the need for on-the-fly calculations. When you invoke spatial processing on the web, it makes your server busy and increases end-user wait times. In the above example, perhaps the nearest hospital to each library could be precalculated in a desktop GIS environment and stored in the library attribute table.

There will be some situations where precalculation doesn't make sense due to the wide range of analysis parameters and possibilities you want to expose (for example, if there were 20 different kinds of features that you wanted to allow people to find near a library, or if a person could type in the address of some library not in the original dataset); therefore, the decision about whether to allow real-time spatial processing should be made on a case-by-case basis.

Implementing spatial data processing in the term project

Unless you already have some previous exposure to WPS, I do not recommend integrating it into your term project given the short amount of time that remains in the course. A Turf.js implementation would be a better fit for the scope of the term project, since it can be implemented fairly quickly with less code.

Neither of these technologies is a requirement for the project, although they may provide useful functionality in many scenarios.

JavaScript libraries and web presentation frameworks

Working with web pages is not always easy, especially when it's not your main area of expertise. Code for laying out the page, iterating through elements, toggling classes, etc., can get clunky and bloated in a hurry. Browsers can interpret the same pieces of HTML and JavaScript differently, and debugging the pages can be a mysterious process involving multiple code files and complex hierarchies of stylesheets and overrides.

There is no magic solution for these challenges; however, there are some JavaScript helper libraries that can simplify your life and your code if you make the effort to learn them. Your pages will probably also become more functional and attractive. Some of these libraries offer mainly back-end functions, others specialize in front-end elements, and others offer both.

JavaScript libraries

Many web developers like to use special JavaScript libraries that have been developed to simplify common functions and abstract away some of the idiosyncrasies between web browsers.

For example, if you select a few web pages at random and look at the source code, chances are pretty good that you'll see someone using the jQuery library. jQuery [25] provides functions to simplify navigating and manipulating the DOM elements on your page. For example, using jQuery you can populate a dropdown list or change the CSS class of an HTML element on the fly without writing a bunch of HTML markup.

Similar alternatives to jQuery are Prototype [26] and the Dojo toolkit [27], although the latter also offers some UI elements such as menus, buttons, and charts. All these libraries are build with the goal of simplifying JavaScript coding and reducing the work for you to handle differences between browsers.

Web presentation frameworks

In the previous lesson examples, you've learned how to embed your map in a web page. In the Lesson 7 walkthrough, you also briefly saw how to use the innerHtml property to manipulate a DOM element and thereby change a label on a page. But how do you craft a nice looking page around your map without devoting hundreds of hours to web design study? This is where a web presentation framework can come in handy.

Web presentation frameworks consist of JavaScript libraries, stylesheets, and "widgets" that work together to give you some convenient building blocks for nice looking pages. Some of these goodies include headers, buttons, calendar date pickers, menus, etc.

Web development frameworks are typically engineered so that your page works in a similar manner across browsers. They may also give you head start with making your page easily localizable (i.e., flexible enough to be offered in various languages such as Spanish, Arabic, Chinese, etc.).

Examples

An example of a popular web presentation framework is Bootstrap [28], distributed by the Twitter development team under an open source license. In the Lesson 8 walkthrough, you'll use Bootstrap to put a nice header on your application and divide your page into sections. You'll also get the benefit of the Bootstrap stylesheets.

Similar frameworks to Bootstrap include Groundwork [29], Zurb's Foundation [30], and Yahoo's Pure [31]. The latter used to be called YUI (yoo'-ee) and this name is still often seen in the web world. Take a few minutes and follow some of the above links to see the different styles offered by these frameworks.

Some JavaScript libraries such as the Dojo toolkit  and Ext JS [32] also offer layout elements that serve a web design function. The project jQuery UI [33] offers a helpful set of user interface elements such as buttons, menus, date pickers, etc.

You should be aware that web presentation frameworks sometimes require and use jQuery or other libraries to accomplish their functions.

Choosing a framework

When evaluating a web presentation framework for your project, you might consider:

  • How easy is it to get started with the framework?
  • How many online examples are available on the framework and is there an active community posting to forums, etc.?
  • How easy is it to customize the framework if I don't like the default choices or examples?
  • Is the framework hosted on a CDN or do I have to host it myself? Hosting yourself is often required anyway if you want to customize or slim down any portion of the framework, but it does take more work.
  • What are the terms of use for this framework, including attribution and licensing requirements? Just like other kinds of software, there are FOSS frameworks and proprietary ones.

A web presentation framework can help produce a nicer end product than you might be able to concoct on your own; however, a framework also introduces another level of complexity. You may have to override some of the framework's stylesheets in order to get other libraries (like Leaflet or OpenLayers) to behave in the expected way. As an example, I have still not figured out how to get OpenLayers framed cloud popups to appear correctly in Bootstrap. Debugging such issues often involves multiple levels of stylesheets and a lot of time in the weeds. If you just need a very simple app, you might leave the framework aside and create your own stylesheet, or at least try to select the most simple framework available for the functionality you need.

Walkthrough: Thematic map with Leaflet controls and Bootstrap framework

In this walkthrough, you'll put together some of the things you've learned in this lesson to make a well-rounded informational map containing thematic styling and Leaflet controls. The map will use the Bootstrap web presentation framework so that it can be extended with supplementary content in an aesthetically pleasing way. You will construct some of this content on the fly by reading the attributes of selected map features.

For this exercise, we'll stick with the metro data from South America. The final application will allow users to click any metro and see some informational text and an image from Flickr. A static legend image is also brought into the layout.

Lesson 8 walkthrough completed
Figure 8.12.

Although this lesson is called "Going beyond 'dots on a map," I have deliberately chosen point data for this walkthrough so that you can appreciate the possibilities offered through Leaflet, especially when tied together with other frameworks. Although you may not feel like a JavaScript guru at this point, you should at least know that you don't have to settle for the 'red teardrop and popup' mashups that you see all over the Internet.

Downloading the data and examining the layout

  1. Download the Lesson 8 walkthrough data [34] and unzip it anywhere. If you like, you could place the files in your Jetty home folder, but the walkthrough does not use GeoServer and this is not required. The files include a legend image, the metro SVG images, and a GeoJSON file of metro locations and attributes. Also note that in a departure from previous walkthroughs, I've already supplied an HTML file containing the basic layout markup. We will examine some of the key pieces of the markup in the steps below. You'll also add some JavaScript to give this app some life.
  2. Open lesson8_walkthrough.html in a web browser and take a look. You should see the empty shell of the app with no map yet.
     Empty shell of Lesson 8 walkthrough showing just the layout.
    Figure 8.13.
  3. Open lesson8_walkthrough.html in your favorite text editor and look over the HTML markup. The layout is based on this old OpenLayers and Bootstrap [35] example.

Before you add JavaScript, I will just point out a few things that are important in the layout. First, notice how Bootstrap is brought into the application through a reference to a JavaScript file (.js) and a CSS file (.css) in the bolded lines below. We are referencing the files from a CDN, but you could alternatively download and host (and tweak) them yourself.

<script src="http://code.jquery.com/jquery-latest.js"></script>
. . .
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/2.2.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/2.2.1/css/bootstrap.min.css">

Also take note of the stylesheet override in the <style> tag. This will prevent our page content from crashing into the top banner.

<style> 
  body { 
    padding-top: 60px; 
    padding-bottom: 40px; 
  } 
</style>

If you don't like something about the Bootstrap (or Leaflet) default styles, the stylesheet overrides are where you can apply a change.

In the page body, observe how classes are used to organize the page into navigation bars (navbar-*), containers (container-fluid), and spans. The span5 and span7 classes divide the page vertically so that consistent proportions of the page area are devoted to the map (on the left) and the supplementary material (on the right). Don't worry about understanding all these classes at this point. Just be aware that web development frameworks like Bootstrap, Dojo, etc., may give you CSS classes for organizing page elements in a way that should behave consistently across browsers.

Finally, note that the legend image and the "Click a metro..." text are organized into an HTML table to keep them flush. The table borders are invisible by design.

<table>
  <tr>
    <td><img src="metro_legend.png"/></td>
    <td id = 'summaryLabel'><p>Click a metro rail system on the map to get more information.</p></td>
  </tr>
</table>

A div awaits for a Flickr image, but this won't appear until someone clicks a metro symbol on the map.

<div id="metroImage"></div>

Now let's breathe some life into this app by adding some JavaScript code.

Adding the JavaScript logic

You'll use JavaScript to do three main things in this application: add the map, define the symbols, and handle the click events. Let's address these in order.

  1. In your lesson8_walkthrough.html, find the script tags. You'll put all your code immediately after: <script type="text/javascript">.

  2. Add the following lines of code to create the map and an initialization function. This function contains some variables that we'll use to handle selections throughout, similar to what you saw in Lesson 7:

    var map;
    var metroLayer;
    
    function init() {
      
      // create map and set center and zoom level
      map = new L.map('mapid');
      map.setView([-28,-62],3);
    
      var selection;
      var selectedLayer;
      var selectedFeature;
    
      . . .
    }
    
  3. Now let's create some layers and put them on the map. The code below creates two tiled basemap layers and the metro layer. It only adds one basemap layer to the map; the other one will eventually be an option in the layer switcher.

    Replace the . . . in the code above with the following. Although it's a lot, all of this should look pretty familiar if you paid attention to the code snippets earlier in the lesson:
    // create and add osm tile layer
    var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });
    osm.addTo(map);
    
    // create stamen osm layer (not adding it to map)
    var stamen = L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, under an <a href="http://www.openstreetmap.org/copyright" title="ODbL">open license</a>. Toner style by <a href="http://stamen.com">Stamen Design</a>'
    });
    
    // create metro icons
    var metroLowIcon = L.icon({
      iconUrl: 'metro_low.svg',
      iconSize: [25,25]
    });
    
    var metroMediumIcon = L.icon({
      iconUrl: 'metro_medium.svg',
      iconSize: [25,25]
    });
    
    var metroHighIcon = L.icon({
      iconUrl: 'metro_high.svg',
      iconSize: [25,25]
    });
    
    var metroSelected = L.icon({
      iconUrl: 'metro_selected.svg',
      iconSize: [25,25]
    });
              
    // add the metro GeoJSON layer
    var metroLayer = L.geoJson(metroData,{
      pointToLayer: function (feature, latlng) {
        return L.marker(latlng, {icon: iconByPassday(feature)});
      },
      onEachFeature: metrosOnEachFeature
    });
    
    metroLayer.addTo(map);
    
    . . .

    You may have noticed that the metroLayer relies on two functions: iconByPassday and metrosOnEachFeature. We'll tackle those in a few minutes. But first, the layer switcher...

  4. Replace the . . . in the code above to add a layer switcher control to the map:

    // define basemap and thematic layers and add layer switcher control
    var basemaps = {
      "OSM": osm,
      "Stamen": stamen
    };
    
    var overlays = {
      "Metro stations": metroLayer
     };
    
    L.control.layers(basemaps,overlays).addTo(map);
    
    . . .
    
    Again, the pattern here is to create a JavaScript object for the basemaps and one for the thematic layers, and then pass those two objects in as parameters when you create the control.
  5. Now for those functions I mentioned. The first one, iconByPassday, looks at the number of passengers per day that travel through each metro system, then decides which icon to use. It's the key to classifying the layer and symbolizing it with the different colors. Replace the . . . in the code above with the following:

    // define functions that right icon for a given feature
    function iconByPassday(feature) {
      var icon;
      if (feature.properties.PASSDAY >= 2000000) icon = metroHighIcon;
      else if (feature.properties.PASSDAY >= 1000000) icon = metroMediumIcon;
      else icon = metroLowIcon;
    
      return icon;
    } 
    
    . . .
  6. Now for that metrosOnEachFeature function. This one is a little more lengthy. It adds a click event listener to the metros layer and then defines a function to handle that event. Replace the . . . in the code above with the following:

    // define function to handle click events on metro features
    function metrosOnEachFeature(feature, layer){
      layer.on({
        click: function(e) {
          // reset symbol of old selection
          if (selection) {
            if (selectedLayer === metroLayer) selection.setIcon(iconByPassday(selectedFeature));
          }
                      
          // apply yellow icon to newly selected metro and update selection variables
          e.target.setIcon(metroSelected);
          selection = e.target;
          selectedLayer = metroLayer;
          selectedFeature = feature;
                      
          // using attributes, construct some HTML to write into the page
          var featureName = feature.properties.CITY || 'Unnamed feature';
          var country = feature.properties.COUNTRY || '(Unknown)';
          var year = feature.properties.YEAR || '(Unknown)';
          var passengers = feature.properties.PASSDAY || '(Unknown)';
          var stations = feature.properties.STATIONS || '(Unknown)';
          var length = feature.properties.LENGTHKM || '(Unknown)';
          var link = feature.properties.LINK || 'http://www.wikipedia.org';
          var photoHtml = feature.properties.PHOTO || '<P>Photo not available</P>';
          var titleHtml = '<p style="font-size:18px"><b>' + featureName + '</b></p>';
          var descripHtml = '<p>The ' + featureName + ', ' + country + ' metro opened in ' + year + ' and currently serves ' + passengers + ' passengers a day. The network consists of ' + stations + ' stations spread over ' + length + ' kilometers.</p>';
          var readmoreHtml = '<p><a href="' + link + '">Read more</a></p>';
          document.getElementById('summaryLabel').innerHTML = titleHtml + descripHtml + readmoreHtml;
          document.getElementById('metroImage').innerHTML = photoHtml;
    
          L.DomEvent.stopPropagation(e); // stop click event from being propagated further
        }
      });
    }
    
    . . .
    Examine the code comments above to understand what each section is doing. There's a lot of code, but most of it is constructing the informational HTML describing the metro system. Recall that the attribute table looks like this:

     Attribute table for metros
    Figure 8.15.

    You should notice many of these attribute field names referenced in the code above. The field value is retrieved, using some special syntax || (logical OR operator that in JavaScript returns the first operand that is TRUE) to set a fallback value in case no attribute comes back. The rest of the function constructs some HTML strings, inserting the attributes where appropriate. After the HTML strings are constructed, the innerHTML is updated for the elements "summaryLabel" and "metroImage." This causes the new text and the photo to appear on the right side of the page.

    The PHOTO field deserves some additional discussion here. Anticipating that this field would be used to embed a photo in an app, the entire iframe HTML code is placed in this field as a long string. Where do you get this code if you want to make a dataset like this? Right from the Flickr embed functionality:

     Getting embed code from Flickr
    Figure 8.16.

  7. Finally, add a click event handler to the map that will reset the selection. This is necessary if someone clicks the map, but not a metro icon. Replace the . . . in the code above with the following:

    // define and register event handler for click events to unselect features when clicked anywhere else on the map
    map.addEventListener('click', function(e) {
      if (selection) {
        if (selectedLayer === metroLayer) selection.setIcon(iconByPassday(selectedFeature));
    
        selection = null;
    
        document.getElementById('summaryLabel').innerHTML = '<p>Click a metro rail system on the map to get more information.</p>';
        document.getElementById('metroImage').innerHTML = ''
      }
    });
    Notice that some code was added here to also clear out the informational HTML and the photo.

  8. Test the application by opening lesson8_walkthrough.html in a web browser. (If you're just opening the HTML file directly from the file system, I recommend using Firefox to test. This is because the cross-origin request security implementations in Chrome and Internet Explorer only allow the GeoJSON to load if you're hosting both the HTML and GeoJSON files on a web server.)

    You should be able to switch between different base layers (note that the Map Quest open layer shown below is no longer available). Click a metro icon to see the highlighted symbol, the Flickr image, and the descriptive text.

     Testing the completed walkthrough
    Figure 8.17.

Final code for the walkthrough

If the walkthrough does not function, check to ensure you are connected to the Internet and that your code matches the code below:

<!DOCTYPE html>
  <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <title>Lage metro rail systems in South America</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" type="text/css" crossorigin="">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js" crossorigin=""></script>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
      <script src="metro.js"></script>
      <link rel="stylesheet" href="style.css" type="text/css">

      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/2.2.1/js/bootstrap.min.js"></script>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/2.2.1/css/bootstrap.min.css">
       
      <style>
        body {
            padding-top: 60px;
            padding-bottom: 40px;
        }

     
      </style>

      <script type="text/javascript">
          
        var map;
        var metroLayer;

        function init() {
          // create map and set center and zoom level
          map = new L.map('mapid');
          map.setView([-28,-62],3);

          var selection;
          var selectedLayer;
          var selectedFeature;

      

          // create and add osm tile layer
          var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
          });
          osm.addTo(map);

          // create stamen osm layer (not adding it to map)
          var stamen = L.tileLayer('http://tile.stamen.com/toner/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '&copy; <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, under an <a href="http://www.openstreetmap.org/copyright" title="ODbL">open license</a>. Toner style by <a href="http://stamen.com">Stamen Design</a>'
          });


          // create metro icons
          var metroLowIcon = L.icon({
            iconUrl: 'metro_low.svg',
            iconSize: [25,25]
          });

          var metroMediumIcon = L.icon({
            iconUrl: 'metro_medium.svg',
            iconSize: [25,25]
          });

          var metroHighIcon = L.icon({
            iconUrl: 'metro_high.svg',
            iconSize: [25,25]
          });

          var metroSelected = L.icon({
            iconUrl: 'metro_selected.svg',
            iconSize: [25,25]
          });
          
          // add the metro GeoJSON layer
       
          var metroLayer = L.geoJson(metroData,{
            pointToLayer: function (feature, latlng) {
              return L.marker(latlng, {icon: iconByPassday(feature)});
            },
            onEachFeature: metrosOnEachFeature
            });
          metroLayer.addTo(map);


          // define basemap and thematic layers and add layer switcher control
          var basemaps = {
            "OSM": osm,
            "Stamen": stamen
          };

          var overlays = {
            "Metro stations": metroLayer
          };
          L.control.layers(basemaps,overlays).addTo(map);
            
          

          // define functions that right icon for a given feature
          function iconByPassday(feature) {
            var icon;
            if (feature.properties.PASSDAY >= 2000000) icon = metroHighIcon;
            else if (feature.properties.PASSDAY >= 1000000) icon = metroMediumIcon;
            else icon = metroLowIcon;

            return icon;
          }

          // define function to handle click events on metro features
          function metrosOnEachFeature(feature, layer){
            layer.on({
              click: function(e) {
                // reset symbol of old selection
                if (selection) {
                  if (selectedLayer === metroLayer) selection.setIcon(iconByPassday(selectedFeature));
                }
                  
                // apply yellow icon to newly selected metro and update selection variables
                e.target.setIcon(metroSelected);
                selection = e.target;
                selectedLayer = metroLayer;
                selectedFeature = feature;
                  
                // using attributes, construct some HTML to write into the page
                var featureName = feature.properties.CITY || 'Unnamed feature';
                var country = feature.properties.COUNTRY || '(Unknown)';
                var year = feature.properties.YEAR || '(Unknown)';
                var passengers = feature.properties.PASSDAY || '(Unknown)';
                var stations = feature.properties.STATIONS || '(Unknown)';
                var length = feature.properties.LENGTHKM || '(Unknown)';
                var link = feature.properties.LINK || 'http://www.wikipedia.org';
                var photoHtml = feature.properties.PHOTO || '<P>Photo not available</P>';
                var titleHtml = '<p style="font-size:18px"><b>' + featureName + '</b></p>';
                var descripHtml = '<p>The ' + featureName + ', ' + country + ' metro opened in ' + year + ' and currently serves ' + passengers + ' passengers a day. The network consists of ' + stations + ' stations spread over ' + length + ' kilometers.</p>';
                var readmoreHtml = '<p><a href="' + link + '">Read more</a></p>';
                document.getElementById('summaryLabel').innerHTML = titleHtml + descripHtml + readmoreHtml;
                document.getElementById('metroImage').innerHTML = photoHtml;

                L.DomEvent.stopPropagation(e); // stop click event from being propagated further
              }
            });
          }
          
          // define and register event handler for click events to unselect features when clicked anywhere else on the map
          map.addEventListener('click', function(e) {
            if (selection) {
              if (selectedLayer === metroLayer) selection.setIcon(iconByPassday(selectedFeature));

              selection = null;

              document.getElementById('summaryLabel').innerHTML = '<p>Click a metro rail system on the map to get more information.</p>';
              document.getElementById('metroImage').innerHTML = ''
            }
          });
          
        }
            
      </script>
    </head>

  <body onload="init()">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="navbar-inner">
            <div class="container-fluid">
                <a class="brand" href="/">Large metro rail systems in South America</a>
            </div>
        </div>
    </div>
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span5">
                <div id="mapid">
                </div>
            </div>
            <div class="span7">      
                <div>
                  <table>
                    <tr>
                      <td><img src="metro_legend.png"/></td>
                      <td id = 'summaryLabel'><p>Click a metro rail system on the map to get more information.</p></td>
                    </tr>
                  </table>
                <div id="metroImage"></div>
              </div>
            </div>
        </div>
    </div>
  </body>
</html>

Lesson 8 assignment: Independent exploration of FOSS

If the amount of JavaScript in the walkthrough was intimidating, don't worry. You don't have to write that kind of code in the lesson assignment, although I hope some of it comes in handy in your term project.

For this week's assignment, we're going to do something a bit different. You will identify and report on some GIS FOSS that interests you. Your typical encounter with FOSS in the "real world" will be open ended with little direction other than what you can find online. Therefore, this assignment is designed to give you an experience of independent exploration.

Reading

First, read this article to get an idea of the many different FOSS products used with GIS, some of which we have not covered in this course. I have placed this article in the Lesson 8 module in Canvas:

  • Steiniger, S., & Hunter, A. J. (2013). The 2012 free and open source GIS software map–A guide to facilitate research, development, and adoption. Computers, Environment and Urban Systems, 39, 136-150.

With its many acronyms, this article approaches alphabet soup at times, but I think you are far enough along in this course that you will recognize some of them and have enough frame of reference to process the ones you don't know. Obviously this article is several years old, but will help get you up to date on the major players in FOSS GIS. I am not aware of a more recent article as thorough as this.

Experimentation

Identify a FOSS solution for GIS that we have not covered in this course, download it (if applicable), and briefly experiment with it. Then use the software to accomplish something useful.

In fulfilling this requirement, keep in mind the following:

  • This should ideally be some software that will be helpful in your term project, although this is not required.
  • APIs, such as OpenLayers and D3, are okay for this requirement.
  • If you select a cloud-based service, be sure that it contains some open source element.

Use the Steiniger and Hunter article, as well as the Ramsey video from Lesson 1, if you need ideas about what to review.

Deliverable

Write a report of around 1000 words describing the following:

  • What software you chose, who makes it, and why.
  • How it relates to web mapping.
  • What useful function you accomplished with it. Provide screenshots.
  • What documentation you found and used in accomplishing the above things. Provide links. Please also comment on its quality.
  • How this software could possibly complement or extend the projects you have completed in this course.

If you reviewed a cloud based service or a FOSS product created by a for-profit entity, please delineate which elements of the software are FOSS and which are not. Also describe how the FOSS portion fits into the business model of the provider.

Remember that free software and APIs are not always open source. The software you review for this assignment must have some portion where the source code is openly shared and can be reused or modified.

You should submit your report into the Lesson 8 assignment drop box on Canvas.

 


Source URL: https://www.e-education.psu.edu/geog585/node/761

Links
[1] https://www.e-education.psu.edu/geog585/node/785
[2] http://data.buenosaires.gob.ar/dataset
[3] http://gis.stackexchange.com/questions/11106/tiny-js-discretization-library-for-choropleth-representation
[4] http://leafletjs.com/reference-1.0.3.html#control-layers
[5] http://leafletjs.com/reference-1.0.3.html#control-zoom
[6] http://workshops.boundlessgeo.com/geoserver-intro/overview/wfs.html
[7] http://suite.opengeo.org/geoserver/wfs?service=wfs&amp;version=1.1.0&amp;request=GetFeature&amp;typename=usa:states&amp;featureid=states.23
[8] https://gis.stackexchange.com/questions/64406/getting-wfs-data-from-geoserver-into-leaflet
[9] http://resources.arcgis.com/en/help/main/10.1/0037/00370000000p000000.htm
[10] https://leaflet.github.io/Leaflet.draw/docs/leaflet-draw-latest.html
[11] https://leaflet.github.io/Leaflet.draw/docs/examples/full.html
[12] https://github.com/respec/leaflet.wfs-t
[13] http://dev.openlayers.org/releases/OpenLayers-2.13.1/examples/wfs-protocol-transactions.html
[14] http://docs.geoserver.org/2.3.5/user/extensions/wps/index.html
[15] https://github.com/locationtech/jts
[16] http://www.zoo-project.org/
[17] http://pywps.org/
[18] http://plugins.qgis.org/plugins/wps/
[19] http://dev.openlayers.org/docs/files/OpenLayers/WPSClient-js.html
[20] http://dev.openlayers.org/releases/OpenLayers-2.13.1/examples/wps-client.html
[21] http://turfjs.org/
[22] https://opensource.org/licenses/MIT
[23] https://npmcdn.com/@turf/turf/turf.min.js
[24] https://www.mapbox.com/help/analysis-with-turf/
[25] http://jquery.com/
[26] http://prototypejs.org/
[27] http://www.dojotoolkit.org
[28] http://www.getbootstrap.com
[29] https://github.com/groundworkcss/groundwork
[30] http://foundation.zurb.com/
[31] http://purecss.io/
[32] http://www.sencha.com/products/extjs/
[33] https://jqueryui.com/
[34] https://www.e-education.psu.edu/geog585/sites/www.e-education.psu.edu.geog585/files/lesson8/lesson8_walkthrough_leaflet.zip
[35] http://dev.openlayers.org/releases/OpenLayers-2.13.1/examples/bootstrap.html