Monday Jul 01, 2013

Thematic map contd.

The previous post (creating a thematic map) described the use of an advanced style (color ranged-bucket style). The bucket style definition object has an attribute ('classification') which specifies the data classification scheme to use. It's values can be one of {'equal', 'quantile', 'logarithmic', 'custom'}. We use logarithmic in the previous example. Here we'll describe how to use a custom algorithm for classification. Specifically the Jenks Natural Breaks algorithm. We'll use the Javascript implementation in geostats.js

The sample code above needs a few changes which are listed below.

Include the geostats.js file after or before including oraclemapsv2.js

<script src="geostats.js"></script>

Modify the bucket style definition to use custom classification

   bucketStyleDef = {
      numClasses : colorSeries[colorName].classes,
      classification: 'custom', //'logarithmic',  // use a logarithmic scale 
      algorithm: jenksFromGeostats,
      styles: theStyles,
      gradient:  useGradient? 'linear' : 'off'
    };

The function, which implements the custom classification scheme, is specified as the algorithm attribute value. It must accept two input parameters, an array of OM.feature and the name of the feature attribute (e.g. TOTPOP) to use in the classification, and must return an array of buckets (i.e. an array of or OM.style.Bucket  or OM.style.RangedBucket in this case).

However the algorithm also needs to know the number of classes (i.e. the number of buckets to create). So we use a global to pass that info in. (Note: This bug/oversight will be fixed and the custom algorithm will be passed 3 parameters: the features array, attribute name, and number of classes).

So createBucketColorStyle() has the following changes

var numClasses ;
function createBucketColorStyle(
colorName, colorSeries, rangeName, useGradient)
{
   var theBucketStyle;
   var bucketStyleDef;
   var theStyles = [];
   //var numClasses ;

numClasses = colorSeries[colorName].classes;
...

and the function jenksFromGeostats is defined as

function jenksFromGeostats(featureArray, columnName)
{
   var items = [] ; // array of attribute values to be classified

   $.each(featureArray, function(i, feature) {
        items.push(parseFloat(feature.getAttributeValue(columnName)));
   });

   // create the geostats object
   var theSeries = new geostats(items);
   // call getJenks which returns an array of bounds
   var theClasses = theSeries.getJenks(
numClasses);
   if(theClasses)
   {
    theClasses[theClasses.length-1]=parseFloat(theClasses[theClasses.length-1])+1;
   }
   else
   {
    alert(' empty result from getJenks');
   }
   var theBuckets = [], aBucket=null ;
   for(var k=0; k<
numClasses; k++)
   {
            aBucket = new OM.style.RangedBucket(
            {low:parseFloat(theClasses[k]),
              high:parseFloat(theClasses[k+1])
            });

            theBuckets.push(aBucket);
    }
    return theBuckets;
}

A screenshot of the resulting map with 5 classes is shown below.


It is also possible to simply create the buckets and supply them when defining the Bucket style instead of specifying the function (algorithm). In that case the bucket style definition object would be

   bucketStyleDef = {
      numClasses : colorSeries[colorName].classes,
      classification: 'custom', 
      buckets: theBuckets, //since we are supplying all the buckets
      styles: theStyles,
      gradient:  useGradient? 'linear' : 'off'
    };



Wednesday Jun 26, 2013

Creating a thematic map

This post describes how to create a simple thematic map, just a state population layer, with no underlying map tile layer. The map shows states color-coded by total population. The map is interactive with info-windows and can be panned and zoomed.

The sample code demonstrates the following:

  • Displaying an interactive vector layer with no background map tile layer (i.e. purpose and use of the Universe object)
  • Using a dynamic (i.e. defined via the javascript client API) color bucket style
  • Dynamically changing a layer's rendering style
  • Specifying which attribute value to use in determining the bucket, and hence style, for a feature (FoI)

The result is shown in the screenshot below.

Screenshot of US state population thematic map


The states layer was defined, and stored in the user_sdo_themes view of the mvdemo schema, using MapBuilder. The underlying table is defined as

SQL> desc states_32775
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 STATE                                              VARCHAR2(26)
 STATE_ABRV                                         VARCHAR2(2)
 FIPSST                                             VARCHAR2(2)
 TOTPOP                                             NUMBER
 PCTSMPLD                                           NUMBER
 LANDSQMI                                           NUMBER
 POPPSQMI                                           NUMBER
...
 MEDHHINC                                           NUMBER
 AVGHHINC                                           NUMBER
 GEOM32775                                          MDSYS.SDO_GEOMETRY

We'll use the TOTPOP column value in the advanced (color bucket) style for rendering the states layers. The predefined theme (US_STATES_BI) is defined as follows.

SQL> select styling_rules from user_sdo_themes where name='US_STATES_BI';

STYLING_RULES
--------------------------------------------------------------------------------

<?xml version="1.0" standalone="yes"?>
<styling_rules highlight_style="C.CB_QUAL_8_CLASS_DARK2_1">
  <hidden_info>
    <field column="STATE" name="Name"/>
    <field column="POPPSQMI" name="POPPSQMI"/>
    <field column="TOTPOP" name="TOTPOP"/>
  </hidden_info>
  <rule column="TOTPOP">
    <features style="states_totpop"> </features>
    <label column="STATE_ABRV" style="T.BLUE_SERIF_10"> 1 </label>
  </rule>
</styling_rules>

SQL>

The theme definition specifies that the state, poppsqmi, totpop, state_abrv, and geom columns will be queried from the states_32775 table. The state_abrv value will be used to label the state while the totpop value will be used to determine the color-fill from those defined in the states_totpop advanced style. The states_totpop style, which we will not use in our demo, is defined as shown below.

SQL> select definition from user_sdo_styles where name='STATES_TOTPOP';

DEFINITION
--------------------------------------------------------------------------------
<?xml version="1.0" ?>
<AdvancedStyle>
   <BucketStyle>
    <Buckets default_style="C.S02_COUNTRY_AREA">
     <RangedBucket seq="0" label="10K - 5M" low="10000" high="5000000" style="C.SEQ6_01" />
      <RangedBucket seq="1" label="5M - 12M" low="5000001" high="1.2E7" style="C.SEQ6_02" />
      <RangedBucket seq="2" label="12M - 20M" low="1.2000001E7" high="2.0E7" style="C.SEQ6_04" />
      <RangedBucket seq="3" label="&gt; 20M" low="2.0000001E7" high="5.0E7" style="C.SEQ6_05" />
    </Buckets>
   </BucketStyle>
</AdvancedStyle>

SQL>

The demo defines additional advanced styles via the OM.style object and methods and uses those instead when rendering the states layer.  

Now let's look at relevant snippets of code that defines the map extent and zoom levels (i.e. the OM.universe),  loads the states predefined vector layer (OM.layer), and sets up the advanced (color bucket) style.

Defining the map extent and zoom levels.
function initMap()
{
  //alert("Initialize map view");
  
  // define the map extent and number of zoom levels.
  // The Universe object is similar to the map tile layer configuration
  // It defines the map extent, number of zoom levels, and spatial reference system
  // well-known ones (like web mercator/google/bing or maps.oracle/elocation are predefined
  // The Universe must be defined when there is no underlying map tile layer. 
  // When there is a map tile layer then that defines the map extent, srid, and zoom levels.
     var uni= new OM.universe.Universe(
    {
        srid : 32775,
        bounds : new OM.geometry.Rectangle(
                        -3280000, 170000, 2300000, 3200000, 32775),
        numberOfZoomLevels: 8
    });

The srid specifies the spatial reference system which is Equal-Area Projection (United States).

SQL> select cs_name from cs_srs where srid=32775 ;
CS_NAME
---------------------------------------------------
Equal-Area Projection (United States)

The bounds defines the map extent. It is a Rectangle defined using the lower-left and upper-right coordinates and srid.

Loading and displaying the states layer

This is done in the states() function. The full code is at the end of this post, however here's the snippet which defines the states VectorLayer.

    // States is a predefined layer in user_sdo_themes
    var  layer2 = new OM.layer.VectorLayer("vLayer2", 
    {
        def:
        {
            type:OM.layer.VectorLayer.TYPE_PREDEFINED, 
            dataSource:"mvdemo", 
            theme:"us_states_bi", 
            url: baseURL,
            loadOnDemand: false
        },
        boundingTheme:true
     });

The first parameter is a layer name, the second is an object literal for a layer config. The config object has two attributes: the first is the layer definition, the second specifies whether the layer is a bounding one (i.e. used to determine the current map zoom and center such that the whole layer is displayed within the map window) or not. The layer config has the following attributes:

type - specifies whether is a predefined one, a defined via a SQL query (JDBC), or in a json-format file (DATAPACK)

theme - is the predefined theme's name

url - is the location of the mapviewer server

loadOnDemand - specifies whether to load all the features or just those that lie within the current map window and load additional ones as needed on a pan or zoom

The code snippet below dynamically defines an advanced style and then uses it, instead of the 'states_totpop' style, when rendering the states layer.

// override predefined rendering style with programmatic one
   var theRenderingStyle = 
     createBucketColorStyle('YlBr5', colorSeries, 'States5', true);
  // specify which attribute is used in determining the bucket (i.e. color) to use for the state
  // It can be an array because the style could be a chart type (pie/bar)
  // which requires multiple attribute columns   
  // Use the STATE.TOTPOP column (aka attribute) value here
   layer2.setRenderingStyle(theRenderingStyle, ["TOTPOP"]);

The style itself is defined in the createBucketColorStyle() function.

Dynamically defining an advanced style

The advanced style used here is a bucket color style, i.e. a color style is associated with each bucket. So first we define the colors and then the buckets. 

    numClasses = colorSeries[colorName].classes;
   // create Color Styles
   for (var i=0; i < numClasses; i++) 
   {
        theStyles[i] = new OM.style.Color(
                     {fill: colorSeries[colorName].fill[i], 
                       stroke:colorSeries[colorName].stroke[i],
                      strokeOpacity: useGradient? 0.25 : 1
                     });
   };

numClasses is the number of buckets. The colorSeries array contains the color fill and stroke definitions and is:

var colorSeries = {
//multi-hue color scheme #10 YlBl. 
"YlBl3": {   classes:3,
                 fill: [0xEDF8B1, 0x7FCDBB, 0x2C7FB8],
                 stroke:[0xB5DF9F, 0x72B8A8, 0x2872A6]
  },
"YlBl5": {   classes:5,
                 fill:[0xFFFFCC, 0xA1DAB4, 0x41B6C4, 0x2C7FB8, 0x253494],
                 stroke:[0xE6E6B8, 0x91BCA2, 0x3AA4B0, 0x2872A6, 0x212F85]
  },
//multi-hue color scheme #11 YlBr.
 "YlBr3": {classes:3,
                 fill:[0xFFF7BC, 0xFEC44F, 0xD95F0E],
                 stroke:[0xE6DEA9, 0xE5B047, 0xC5360D] 
  },
"YlBr5": {classes:5,
                 fill:[0xFFFFD4, 0xFED98E, 0xFE9929, 0xD95F0E, 0x993404],
                 stroke:[0xE6E6BF, 0xE5C380, 0xE58A25, 0xC35663, 0x8A2F04]
    },

etc.

Next we create the bucket style.

   bucketStyleDef = {
      numClasses : colorSeries[colorName].classes,
//      classification: 'custom',  //since we are supplying all the buckets
//      buckets: theBuckets,
      classification: 'logarithmic',  // use a logarithmic scale 
      styles: theStyles,
      gradient:  useGradient? 'linear' : 'off'
//      gradient:  useGradient? 'radial' : 'off'
    };
   theBucketStyle = new OM.style.BucketStyle(bucketStyleDef);
   return theBucketStyle;

A BucketStyle constructor takes a style definition as input. The style definition specifies the number of buckets (numClasses), a classification scheme (which can be equal-ranged, logarithmic scale, or custom), the styles for each bucket, whether to use a gradient effect, and optionally the buckets (required when using a custom classification scheme).

The full source for the demo
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Oracle Maps V2 Thematic Map Demo</title>

<script src="http://localhost:8080/mapviewer/jslib/v2/oraclemapsv2.js" type="text/javascript">
</script>

<script type="text/javascript">
//var $j = jQuery.noConflict();
var baseURL="http://localhost:8080/mapviewer";  // location of mapviewer
OM.gv.proxyEnabled =false;			// no mvproxy needed
OM.gv.setResourcePath(baseURL+"/jslib/v2/images/");  // location of resources for UI elements like nav panel buttons
var map = null;											 // the client mapviewer object 
var statesLayer = null, stateCountyLayer = null;		 // The vector layers for states and counties in a state
var layerName="States";
// initial map center and zoom
var mapCenterLon = -20000;
var mapCenterLat =  1750000;
var mapZoom      =  2;  
var mpoint = new OM.geometry.Point(mapCenterLon,mapCenterLat,32775);
var currentPalette = null, currentStyle=null;

// set an onchange listener for the color palette select list
// initialize the map
// load and display the states layer
$(document).ready( function()
{

      $("#demo-htmlselect").change(function() {
			var theColorScheme = $(this).val();
			useSelectedColorScheme(theColorScheme);
		});
      initMap();
      states();	  
	  
}
);

/**
 * color series from ColorBrewer site (http://colorbrewer2.org/).
 */

var colorSeries = {
  
//multi-hue color scheme #10 YlBl.
  
"YlBl3": {   classes:3,
                 fill: [0xEDF8B1, 0x7FCDBB, 0x2C7FB8],
                 stroke:[0xB5DF9F, 0x72B8A8, 0x2872A6]
  },
  
"YlBl5": {   classes:5,
                 fill:[0xFFFFCC, 0xA1DAB4, 0x41B6C4, 0x2C7FB8, 0x253494],
                 stroke:[0xE6E6B8, 0x91BCA2, 0x3AA4B0, 0x2872A6, 0x212F85]
  },
  
//multi-hue color scheme #11 YlBr.
 
 "YlBr3": {classes:3,
                 fill:[0xFFF7BC, 0xFEC44F, 0xD95F0E],
                 stroke:[0xE6DEA9, 0xE5B047, 0xC5360D] 
  },
  
"YlBr5": {classes:5,
                 fill:[0xFFFFD4, 0xFED98E, 0xFE9929, 0xD95F0E, 0x993404],
                 stroke:[0xE6E6BF, 0xE5C380, 0xE58A25, 0xC35663, 0x8A2F04]
    },
// single-hue color schemes (blues, greens, greys, oranges, reds, purples)
"Purples5": {classes:5,
                 fill:[0xf2f0f7, 0xcbc9e2, 0x9e9ac8, 0x756bb1, 0x54278f],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    },
"Blues5": {classes:5,
                 fill:[0xEFF3FF, 0xbdd7e7, 0x68aed6, 0x3182bd, 0x18519C],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    },
"Greens5": {classes:5,
                fill:[0xedf8e9, 0xbae4b3, 0x74c476, 0x31a354, 0x116d2c],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    },  
"Greys5": {classes:5,
                 fill:[0xf7f7f7, 0xcccccc, 0x969696, 0x636363, 0x454545],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    },
"Oranges5": {classes:5,
                 fill:[0xfeedde, 0xfdb385, 0xfd8d3c, 0xe6550d, 0xa63603],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    },
"Reds5": {classes:5,
                 fill:[0xfee5d9, 0xfcae91, 0xfb6a4a, 0xde2d26, 0xa50f15],
                 stroke:[0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3, 0xd3d3d3]
    }
};

function createBucketColorStyle(
colorName, colorSeries, rangeName, useGradient)
{
   var theBucketStyle;
   var bucketStyleDef;
   var theStyles = [];
   var theColors = [];
   var aBucket, aStyle, aColor, aRange;
   var numClasses ;

   numClasses = colorSeries[colorName].classes;

   // create Color Styles
   for (var i=0; i < numClasses; i++) 
   {
 
        theStyles[i] = new OM.style.Color(
                     {fill: colorSeries[colorName].fill[i], 
                       stroke:colorSeries[colorName].stroke[i],
                      strokeOpacity: useGradient? 0.25 : 1
                     });
   };

   bucketStyleDef = {
      numClasses : colorSeries[colorName].classes,
//      classification: 'custom',  //since we are supplying all the buckets
//      buckets: theBuckets,
      classification: 'logarithmic',  // use a logarithmic scale 
      styles: theStyles,
      gradient:  useGradient? 'linear' : 'off'
//      gradient:  useGradient? 'radial' : 'off'
    };


   theBucketStyle = new OM.style.BucketStyle(bucketStyleDef);


   return theBucketStyle;
}

function initMap()
{
  //alert("Initialize map view");
  
  // define the map extent and number of zoom levels.
  // The Universe object is similar to the map tile layer configuration
  // It defines the map extent, number of zoom levels, and spatial reference system
  // well-known ones (like web mercator/google/bing or maps.oracle/elocation are predefined
  // The Universe must be defined when there is no underlying map tile layer. 
  // When there is a map tile layer then that defines the map extent, srid, and zoom levels.
     var uni= new OM.universe.Universe(
	{
		srid : 32775,
		bounds : new OM.geometry.Rectangle(
                        -3280000, 170000, 2300000, 3200000, 32775),
		numberOfZoomLevels: 8
	});
  map = new OM.Map(
    	document.getElementById('map'),
    	{
          mapviewerURL: baseURL,
    	  universe:uni
    	}) ;
        
  var navigationPanelBar = new OM.control.NavigationPanelBar();
  map.addMapDecoration(navigationPanelBar);
} // end initMap

function states()
{
  
  //alert("Load and display states");
     layerName = "States";
     
     if(statesLayer) 
     { 
       // states were already visible but the style may have changed
       // so set the style to the currently selected one
       var theData = $('#demo-htmlselect').val();
       setStyle(theData); 
     }
     else 
     {
     // States is a predefined layer in user_sdo_themes
     var  layer2 = new OM.layer.VectorLayer("vLayer2", 
        {
          def:
          {
			type:OM.layer.VectorLayer.TYPE_PREDEFINED, 
			dataSource:"mvdemo", 
			theme:"us_states_bi", 
			url: baseURL,
			loadOnDemand: false
  	      },
		boundingTheme:true
  	  });

   // add drop shadow effect and hover style
   var shadowFilter = new OM.visualfilter.DropShadow({opacity:0.5, color:"#000000", offset:6, radius:10});


   var hoverStyle = new OM.style.Color(
        {stroke:"#838383", strokeThickness:2});

   layer2.setHoverStyle(hoverStyle);
   layer2.setHoverVisualFilter(shadowFilter);

   layer2.enableFeatureHover(true);

   layer2.enableFeatureSelection(false);
   layer2.setLabelsVisible(true);
 
// override predefined rendering style with programmatic one

   var theRenderingStyle = 
     createBucketColorStyle('YlBr5', colorSeries, 'States5', true);

  // specify which attribute is used in determining the bucket (i.e. color) to use for the state
  // It can be an array because the style could be a chart type (pie/bar)
  // which requires multiple attribute columns   
  // Use the STATE.TOTPOP column (aka attribute) value here
   layer2.setRenderingStyle(theRenderingStyle, ["TOTPOP"]);
  
   currentPalette = "YlBr5";

   var stLayerIdx =   map.addLayer(layer2);
   //alert('State Layer Idx = ' + stLayerIdx);

   map.setMapCenter(mpoint);
  
   map.setMapZoomLevel(mapZoom) ;
   
   // display the map
   map.init() ;

   statesLayer=layer2;

   // add rt-click event listener to show counties for the state
   layer2.addListener(OM.event.MouseEvent.MOUSE_RIGHT_CLICK,stateRtClick);
   } // end if 

} // end states

function setStyle(styleName) 
{
  // alert("Selected Style = " + styleName);

  // there may be a counties layer also displayed.
  // that wll have different bucket ranges so create 
  // one style for states and one for counties
  var newRenderingStyle = null; 
  if (layerName === "States") 
  {
    if(/3/.test(styleName)) 
    {
     newRenderingStyle = 
     createBucketColorStyle(styleName, colorSeries, 'States3', false);
	 currentStyle = 
	 createBucketColorStyle(styleName, colorSeries, 'Counties3', false);
    }
    else 
    {
     newRenderingStyle = 
     createBucketColorStyle(styleName, colorSeries, 'States5', false);
	 currentStyle = 
	 createBucketColorStyle(styleName, colorSeries, 'Counties5', false);
    }   
    statesLayer.setRenderingStyle(newRenderingStyle, ["TOTPOP"]);
	if (stateCountyLayer)
	 stateCountyLayer.setRenderingStyle(currentStyle, ["TOTPOP"]);
  }
} // end setStyle

function stateRtClick(evt){
  var foi = evt.feature;
  //alert('Rt-Click on State: ' + foi.attributes['_label_'] + 
  //      ' with pop ' + foi.attributes['TOTPOP']);

  // display another layer with counties info 

  // layer may change on each rt-click so create and add each time.
  var countyByState = null ;
  // the _label_ attribute of a feature in this case is the state abbreviation
  // we will use that to query and get the counties for a state
  var sqlText =
"select totpop,geom32775 from counties_32775_moved where state_abrv="+
      "'"+foi.getAttributeValue('_label_')+"'";


// alert(sqlText);

  if (currentStyle === null)
    currentStyle = 
     createBucketColorStyle('YlBr5', colorSeries, 'Counties5', false);
  /* try a simple style instead   
    new OM.style.ColorStyle(
		   {
                      stroke: "#B8F4FF",
		      fill: "#18E5F4",
                      fillOpacity:0
	           }
     );
   */
  // remove existing layer if any
  if(stateCountyLayer) 
    map.removeLayer(stateCountyLayer);

  countyByState = new OM.layer.VectorLayer("stCountyLayer", 
                  {def:{type:OM.layer.VectorLayer.TYPE_JDBC,
                   dataSource:"mvdemo",
                   sql:sqlText,
                   url:baseURL}});			   
//                   url:baseURL},
//                   renderingStyle:currentStyle});

  countyByState.setVisible(true);
  // specify which attribute is used in determining the bucket (i.e. color) to use for the state
  countyByState.setRenderingStyle(currentStyle, ["TOTPOP"]);

   var ctLayerIdx =   map.addLayer(countyByState);
   // alert('County Layer Idx = ' + ctLayerIdx);

  //map.addLayer(countyByState);
  stateCountyLayer = countyByState;
} // end stateRtClick

function useSelectedColorScheme(theColorScheme)
{
   if(map) 
   {
      // code to update renderStyle goes here
	  //alert('will try to change render style');
	  setStyle(theColorScheme);
   }
   else
   {
    // do nothing 
   }
}

</script>
</head>

<body bgcolor="#b4c5cc" style="height:100%;font-family:Arial,Helvetica,Verdana">

<h3 align="center">State population thematic map </h3>
<div id="demo" style="position:absolute; left:68%; top:44px; width:28%; height:100%">
<HR/>
<p/>
Choose Color Scheme:
<select id="demo-htmlselect">
<option value="YlBl3">
YellowBlue3</option>
<option value="YlBr3">
YellowBrown3</option>
<option value="YlBl5">
YellowBlue5</option>
<option value="YlBr5" selected="selected">
YellowBrown5</option>
<option value="Blues5">
Blues</option>
<option value="Greens5">
Greens</option>
<option value="Greys5">
Greys</option>
<option value="Oranges5">
Oranges</option>
<option value="Purples5">
Purples</option>
<option value="Reds5">
Reds</option>
</select>
<p/>

</div>
<div id="map" style="position:absolute; left:10px; top:50px; width:65%; height:75%; background-color:#778f99"></div>
<div style="position:absolute;top:85%; left:10px;width:98%" class="noprint">
<HR/>
<p> Note: This demo uses HTML5 Canvas and requires IE9+, Firefox 10+, or Chrome. No map will show up in IE8 or earlier.
</p>
</div>
</body>
</html>


Wednesday Jun 19, 2013

Upcoming MapViewer webinar (on V2/html5 API) June 27th 11AM ET

This is a repeat of LJ's Spatial User conference session on MapViewer application development. Will cover the following:

- Intro to the HTML5 API

- Templates

If you're interested contact us via email or this blog

Monday Jun 10, 2013

Another sample using the V2 API (but with json files)

In this example we'll show how to display content from a json file on a background map tile layer. So the interactive (vector layer) content is from a file (or datapack) rather a database instance.

The sample application displays lines from USA (DC) to (capitals of) other countries color coded by the number of US visitors to that country in 2009, as shown in the screenshot below.

The data is stored, and read, from a json file which looks something like:

{"type": "FeatureCollection",
  "collectionName": "theme0",
 "srs": 8307,
  "geodetic": true,
  "bbox": [-180, -41.28054, 180,88.93291],
  "attr_names": ["COUNTRY_CODE","COUNTRY","NUM_VISITORS"],
  "attr_types": ["string","string","double"],
  "features": [{"type": "Feature","_id": "MEX",
                    "geometry": {"type": "LineString",
                                      "coordinates": [-77.03201,38.88951,-77.21086,38.77556, ... ]},
  properties": {"COUNTRY_CODE": "MEX","COUNTRY": "MEXICO","NUM_VISITORS": "1945000"}
 }, ... 

The attr_name and attr_types is a extension of the geojson format used for Mapviewer specific reasons.

Broadly speaking, the application code does the following:

Sets up the client map object.
Sets the url for the mapviewer instance and the resources (images for UI components) it needs.
Adds a tile layer.
Adds a country layer.
Adds a layer for visitors from US to other countires (flows layer).
Sets up the color scheme for the flows layer.
Sets up the mouseover interaction and tooltips.

The code is

<!DOCTYPE html>
<html>
  <head>
    <title>Number of US visitors to abroad in 2009</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="/mvdemo/gv.js" type="text/javascript"></script>
    <script>
      document.write('<'+'script src="'+MV.gv.mapviewer_baseURL+
      '/jslib/v2/oraclemapsv2.js"'+' type="text/javascript"><'+'/script>');
    </script>
    <script language="JavaScript" type="text/javascript">
      OM.gv.proxyEnabled =false;
      OM.gv.setResourcePath(MV.gv.mapviewer_baseURL+"/jslib/v2/images/");

      var map = null;
      var flowsLayer = null;
      var countriesLayer = null;
 
      $(document).ready(function()
      {        
        var baseURL  = MV.gv.mapviewer_baseURL;
        var center = new OM.geometry.Point(20,30,8307); 
        map = new OM.Map(document.getElementById('map'),
        {mapviewerURL: baseURL}) ;
                
        var tilelayer = new OM.layer.ElocationTileLayer("oracle.maps");
        map.addLayer(tilelayer) ;  
        
        
        countriesLayer = new OM.layer.VectorLayer("countries",
        {
          def:{
            type:OM.layer.VectorLayer.TYPE_DATAPACK,
            url:"/mvdemo/datapacks/world_countries.json"
          }
        }
      );
        
        var colorStyle = new OM.style.Color({"stroke":"#cc9999",
          strokeThickness:1, strokeOpacity:0.8});
        countriesLayer.setRenderingStyle(colorStyle);
        countriesLayer.enableInfoWindow(false);
        countriesLayer.enableFeatureSelection(true);
        countriesLayer.setSelectStyle(new OM.style.Color(
                       {fill:"#ff0000", fillOpacity:0.8,stroke:"#cc9999", 
                        strokeThickness:4, strokeOpacity:0.5}))
        map.addLayer(countriesLayer);
        
        flowsLayer = new OM.layer.VectorLayer("flows", 
        {
          def:{
            type:OM.layer.VectorLayer.TYPE_DATAPACK, 
             url:"usa_visitors_geodetic.json"
          }
        });          
        flowsLayer.addListener(OM.event.MouseEvent.MOUSE_OUT, hideCountry);
        flowsLayer.addListener(OM.event.MouseEvent.MOUSE_OVER, showCountry);
       
        var hoverStyle = new OM.style.Color({fill:"#ffffff"});
        flowsLayer.setBringToTopOnMouseOver(false);
        flowsLayer.setToolTipCustomizer(getTooltip);
        flowsLayer.enableInfoWindow(false);
        flowsLayer.enableFeatureHover(true);
        flowsLayer.setHoverStyle(hoverStyle);
        
        setupFlowStyles(flowsLayer);
        map.addLayer(flowsLayer);
        
        map.setMapCenter( center );
        map.setMapZoomLevel(2) ;
        map.init() ;
      });
      
      function getTooltip(feature)
      {
        return feature.getAttributes()["COUNTRY"]+", "+feature.getAttributes()["NUM_VISITORS"];
      }
      
      function setupFlowStyles(layer)
      {
        var lines = [];
        var fillColors = 
        // 10 color multihue sequential OrRd
//['#FFF9F2','#FFF7EC','#FEE8C8','#FDD49E','#FDBB84','#FC8D59','#EF6548','#D7301F','#B30000','#7F0000'];
        // 10 color qualitative paired
['#A6CEE3','#B2DF8A','#FB9A99','#FDBF6F','#CAB2D6','#FF7F00','#1F78B4',
'#6A3D9A','#E31A1C','#33A02C'];
        for(var i=0; i<10; i++)
        {
          var lineStyle = new OM.style.Line({fillWidth:6, fill:fillColors[i], 
                                             strokeThickness:1, stroke:fillColors[i]});
          lines[i] = lineStyle;
        }
        
        var bucketStyle = new OM.style.BucketStyle(
        {
          styles: lines,
          numClasses:10, 
          classification:'logarithmic',  
          defaultStyle: lines[0]
        });
        
        layer.setRenderingStyle(bucketStyle, ["NUM_VISITORS"]);
        
      }
     
      function showCountry(evt)
      {
        var feature = evt.feature;
        var selectedFeature;
        var destFeature = countriesLayer.getFeature(feature.id);
        if (destFeature)
        {
          selectedFeature=countriesLayer.selectFeature(destFeature);
          //countriesLayer.bringForward();
          document.getElementById("text").innerHTML = "# of visitors from USA to "+ 
          feature.getAttributes()["COUNTRY"] + " = " + 
          feature.getAttributes()["NUM_VISITORS"];
        }
                
      }

      function hideCountry(evt)
      {
        var feature = evt.feature;
        var selectedFeature;
        var destFeature = countriesLayer.getFeature(feature.id);
        if (destFeature)
        {
          selectedFeature = countriesLayer.deselectFeature(destFeature);
          //countriesLayer.sendBackward();
        }
      }
    </script>       
  </head>
  <body>
    <DIV id="map" style="width:99%;height:94%"></DIV>
    <span id="text">Country: </span>
  </body>
</html>

There are two vector layers. The flows layers is the number of visitors from the US while the countries layer (country boundary) is used to highlight the destination country on a mouse-over.

Now let's go through various code snippets.

The setResourcePath() method is used to tell the client lib where the images and icons, for the various UI components such as the navigation panel, can be found.

After setting the initial map center and background tile layer (oracle.maps) we add the countries vector layer from a json file (OM.layer.VectorLayer.TYPE_DATAPACK).

        countriesLayer = new OM.layer.VectorLayer("countries",
        {
          def:{
            type:OM.layer.VectorLayer.TYPE_DATAPACK,
            url:"/mvdemo/datapacks/world_countries.json"
          }
        }
      );

Then we set up it rendering (line) and selection (red color fill) styles. And similarly the flows layer,

       flowsLayer = new OM.layer.VectorLayer("flows", 
        {
          def:{
            type:OM.layer.VectorLayer.TYPE_DATAPACK, 
            // json file is in same directory as the html (sample code) file  
            url:"usa_visitors_geodetic.json"
          }
        });          

with listener for mouse over and mouse out to handle the highlighting and updating the message area.

        flowsLayer.addListener(OM.event.MouseEvent.MOUSE_OUT, hideCountry);
        flowsLayer.addListener(OM.event.MouseEvent.MOUSE_OVER, showCountry);

Next we set up the hover style, enable info-windows, and the advanced styles for the layer itself. The advanced style is defined and used in setupFlowStyles(flowsLayer) which defines 10 line styles of the specified colors, and then a bucket style using the line styles and a built-in classification scheme.

      function setupFlowStyles(layer)
      {
        var lines = [];
        // colors from colorbrewer2.org 
        var fillColors = 
        // 10 color multihue sequential OrRd
//['#FFF9F2','#FFF7EC','#FEE8C8','#FDD49E','#FDBB84','#FC8D59','#EF6548','#D7301F','#B30000','#7F0000'];
        // 10 color qualitative paired
['#A6CEE3','#B2DF8A','#FB9A99','#FDBF6F','#CAB2D6','#FF7F00','#1F78B4','#6A3D9A',
'#E31A1C','#33A02C'];
        for(var i=0; i<10; i++)
        {
          var lineStyle = new OM.style.Line({fillWidth:6, fill:fillColors[i], 
                                             strokeThickness:1, stroke:fillColors[i]});
          lines[i] = lineStyle;
        }
        
        var bucketStyle = new OM.style.BucketStyle(
        {
          styles: lines,
          numClasses:10, 
          classification:'logarithmic',  //built-in scheme 
          defaultStyle: lines[0]
        });
        
        layer.setRenderingStyle(bucketStyle, ["NUM_VISITORS"]);
        
      }

The setRenderingStyle specifies the bucketStyle to use and the attribute column whose value determines the bucket or style for a given feature.

Lastly, the event listeners showCountry and hideCountry highlight and de-highlight the destination country. The id attribute of a flowsLayer feature is the country code (e.g. "_id": "MEX") and this is also the id column in the countries layer. Since the feature itself is passed in to mouse-over event handler we can use its id to fetch, and highlight, the destination country from the countries layer.

This is done in the showCountry() method

 

      function showCountry(evt)
      {
        var feature = evt.feature;
        var selectedFeature;
        var destFeature = countriesLayer.getFeature(feature.id);
        if (destFeature)
        {
          selectedFeature=countriesLayer.selectFeature(destFeature);
          //update the message area
          document.getElementById("text").innerHTML = "# of visitors from USA to "
          + feature.getAttributes()["COUNTRY"] + " = " + 
          feature.getAttributes()["NUM_VISITORS"];
        }              
      }



MapViewer 11.1.1.7.1 new config settings for vector layers

In 11.1.1.7.1 the data server which sends vector layers (theme-based FOI) to the client is restricted by default. The config file mds.xml (located in the same directory as mapviewerConfig.xml) has to be updated to allow the data server to access and serve up layers from desired data sources.

The relevant readme.txt entry is

- Map Data Server (MDS), the vector theme streaming server of MapViewer, is now in 
  lock-down mode by default and will not allow themes to be streamed to a client unless 
  explicitly configured otherwise. You will need to manually edit the 
  WEB-INF/conf/mds.xml file to control which data sources and which themes can be 
  streamed to a HTML5 application client. 
  A MapViewer restart is required for the changes to take effect.

OBIEE 11.1.1.7 MapViewer error when previewing tile layers

In OBIEE 11.1.1.7 (with MapViewer 11.1.1.7 deployed in Weblogic) you may see an 500 - internal server error when previewing map tile layers in the MapViewer manage map tile layers admin page.

If so you need to modify the web.xml as described in the readme.

On certain WLS domains (mostly when ADF run-time library is deployed), you
  may see errors when trying to preview tile layers. To fix this issue, modify
the weblogic.xml descriptor file found in mapviewer.ear's WEB-INF/ folder, and
uncomment the entry for the jstl 1.2 library as shown below (note the jsf
section should remain commented-out):

<library-ref>
    <library-name>jstl</library-name>
    <specification-version>1.2</specification-version>
  </library-ref>

<!--
<library-ref>
    <library-name>jsf</library-name>
    <specification-version>2.0</specification-version>
    <exact-match>false</exact-match>
</library-ref>
-->

If you upgraded obiee from a previous 11.1.1.x release you may see this error anytime you access the mapviewer admin pages. The fix is to redeploy mapviewer.


Thursday Jun 06, 2013

A live MapViewer instance with HTML5 demos and tutorials

Hi,

  You can now access a public MapViewer 11.1.1.7.1 server and the companion Samples App using the latest MVDEMO sample data set here:

 A few notable links:

 

You can also find online Java and JS API docs under the sample  app (http://slc02okf.oracle.com/mvdemo) and many other demos.

Thanks

LJ 


 

Friday May 24, 2013

A sample application using the V2 API

This entry describes a simple Oracle Maps V2 application which displays a predefined vector layer (aka theme) on a tile layer. The vector layer is called 'Customers' and the tile layer is named 'demo_map'.  The vector layer is based on a database table named CUSTOMERS and it's definition (in user_sdo_themes) is as follows:

SQL> select base_table, geometry_column, styling_rules from user_sdo_themes
  2  where name='CUSTOMERS';

BASE_TABLE
----------------------------------------------------------------
GEOMETRY_COLUMN
-------------------------------------------------------------------------------
STYLING_RULES
-------------------------------------------------------------------------------
CUSTOMERS
LOCATION
<?xml version="1.0" standalone="yes"?>
<styling_rules key_column="NAME">
  <hidden_info>
    <field column="NAME" name="Name"/>
    <field column="CITY" name="City"/>
    <field column="SALES" name="Sales"/>
    <field column="ACCOUNT_MGR" name="Account Manager"/>
  </hidden_info>
  <rule>
    <features style="M.SMALL TRIANGLE"> </features>
    <label column="NAME" style="T.RED STREET"> 1 </label>
  </rule>
</styling_rules>

 The above means that the predefined vector layer will select the name, city, sales, account_mgr, and location columns from the customers table and render the location using the marker style named "M.SMALL TRIANGLE" and label the locations (when possible) with the value of the NAME column using the text styles "T.RED STREET".

The end result will look something like the screenshot below.


The source code for the sample application is:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE>A sample Oracle Maps V2 application</TITLE>
<script language="Javascript" src="/mapviewer/jslib/v2/oraclemapsv2.js"></script>
<script language=javascript>
var customersLayer=null;

$(document).ready(function() {
  var baseURL  = "http://"+document.location.host+"/mapviewer";
  // Create an OM.Map instance to display the map
  var mapview = new OM.Map(document.getElementById("map"), 
                           {
                             mapviewerURL:baseURL
                           });
  // Add a map tile layer as background.
  var tileLayer = new OM.layer.TileLayer(
        "baseMap", 
        {
            dataSource:"mvdemo", 
            tileLayer:"demo_map", 
            tileServerURL:baseURL+"/mcserver"
        });
  mapview.addLayer(tileLayer);
  // Set the initial map center and zoom level
  var mapCenterLon = -122.45;
  var mapCenterLat = 37.7706;
  var mapZoom = 4;
  var mpoint = new OM.geometry.Point(mapCenterLon,mapCenterLat,8307);
  mapview.setMapCenter(mpoint);   
  mapview.setMapZoomLevel(mapZoom);    
  // Add a theme-based FOI layer to display customers on the map
  customersLayer = new OM.layer.VectorLayer("customers", 
        {
            def:
                {
                type:OM.layer.VectorLayer.TYPE_PREDEFINED, 
                dataSource:"mvdemo", theme:"customers", 
                url: baseURL,
                loadOnDemand: false
                }
        });
  mapview.addLayer(customersLayer);

  // Add a navigation panel on the right side of the map
  var navigationPanelBar = new OM.control.NavigationPanelBar();
  navigationPanelBar.setStyle({backgroundColor:"#FFFFFF",buttonColor:"#008000",size:12});
  mapview.addMapDecoration(navigationPanelBar);
  // Add a scale bar
  var mapScaleBar = new OM.control.ScaleBar();
  mapview.addMapDecoration(mapScaleBar);
  // Display the map.
  // Note: Change from V1. In V2 initialization and initial display is done just once
  mapview.init();
});

function setLayerVisible(checkBox)
{
  // Show the customers vector layer if the check box is checked and
  // hide it otherwise.
  if(checkBox.checked)
    customersLayer.setVisible(true) ;
  else
    customersLayer.setVisible(false);
}
</script>
</head>

<body>
<h2>A Sample Oracle Maps V2 Application</h2>
<INPUT TYPE="checkbox" onclick="setLayerVisible(this)" checked/>Show customers
<div id="map" style="width: 600px; height: 500px"></div> 
</body>
</html>

The above code has an html portion and some javscript code which uses the Oracle Maps API.

The html portion consists of a header, a checkbox to determine whether the customers layer is displayed or hidden, and a DIV 600 pixels in width and 500 pixels in height.

The javascript code first loads the oraclemapsv2 library

<script language="Javascript" src="/mapviewer/jslib/v2/oraclemapsv2.js"></script>

 and since oraclemapsv2.js also includes and loads jquery (1.7.2) we can now use the jquery object (or its alias $) and its methods within our code. So we use the ready() method to specify the function, containing our OracleMaps code, to execute when the DOM is ready. The OracleMaps code must do the following:

Create an OM.Map object and associate it with the HTML DIV where the map will be displayed.
Create one or more tile layers (i.e. the background map)
Set a map center and zoom level
Optionally, add one or more interactive vector layers
Optionally, add some map controls and decorations such as a navigation panel and scale bar
Initialize and display the map

The first step is done in the following code snippet:

 

  var baseURL  = "http://"+document.location.host+"/mapviewer";
  // Create an OM.Map instance to display the map
  var mapview = new OM.Map(document.getElementById("map"), 
                           {
                             mapviewerURL:baseURL
                           });

An OM.Map object needs to know where to display a map and which mapviewer instance will handle its requests. So there are two parameters, the "map" DIV and the mapviewerURL.
The next step is to define the background map, or the tile layers.

 

  // Add a map tile layer as background.
  var tileLayer = new OM.layer.TileLayer(
        "baseMap", 
        {
            dataSource:"mvdemo", 
            tileLayer:"demo_map", 
            tileServerURL:baseURL+"/mcserver"
        });
  mapview.addLayer(tileLayer);

A tile layer comes from a specified datasource, has a unique name, and is rendered by a specified mapviewer instance. Now we need to specify the initial map center and zoom level.

  // Set the initial map center and zoom level
  var mapCenterLon = -122.45;
  var mapCenterLat = 37.7706;
  var mapZoom = 4;
  var mpoint = new OM.geometry.Point(mapCenterLon,mapCenterLat,8307);
  mapview.setMapCenter(mpoint);   
  mapview.setMapZoomLevel(mapZoom);    

The Point constructor takes a X, Y and an SRID. In this case it is 8307 which is the SRID for spatial reference system commonly used for GPS devices.

SQL> select cs_name from cs_srs where srid=8307;
CS_NAME
--------------------------------------------------------------------------------
Longitude / Latitude (WGS 84)

Once that's done we add an interactive vector layer.

 

  // Add a vector(theme-based FOI) layer to display customers on the map
  customersLayer = new OM.layer.VectorLayer("customers", 
        {
            def:
                {
                type:OM.layer.VectorLayer.TYPE_PREDEFINED, 
                dataSource:"mvdemo", theme:"customers", 
                url: baseURL,
                loadOnDemand: false
                }
        });
  mapview.addLayer(customersLayer);

Like a tile layer, a predefined vector layer comes from a specified datasource, has a unique name, and is rendered by a specified mapviewer instance.

Next we add some map decorations and controls and finally initialize and display the map.

  // Add a navigation panel on the right side of the map
  var navigationPanelBar = new OM.control.NavigationPanelBar();
  navigationPanelBar.setStyle({backgroundColor:"#FFFFFF",buttonColor:"#008000",size:12});
  mapview.addMapDecoration(navigationPanelBar);
  
// Add a scale bar
  var mapScaleBar = new OM.control.ScaleBar();
  mapview.addMapDecoration(mapScaleBar);

  // Display the map.
  // Note: Change from V1. In V2 initialization and initial display is done just once
  mapview.init();

The last piece is the function which toggles the display of the Customers layer.

 

function setLayerVisible(checkBox)
{
  // Show the customers vector layer if the check box is checked and
  // hide it otherwise.
  if(checkBox.checked)
    customersLayer.setVisible(true) ;
  else
    customersLayer.setVisible(false);
}
 
  

Tuesday May 07, 2013

Intro to the V2 API

So what is the V2 API? Same as the V1 (oracle maps javascript) API but different :-)

The key differences are:

  •  Client side rendering of themes (or theme-based FOI) using SVG or html5 canvas. Themes are called vector layers in V2. FOI are called Features.

  • A map tile layer is not required in order to display a vector layer (theme). That is, a layer (such as county_population_density) can be displayed just by itself and have specified (fixed) zoom levels without any background tile layer. In other words you can have interactive, zoom-able, thematic maps.
  • A namespace (OM) and a different way of specifying parameters. (This is best explained by examples and will be done below)
  • Support for json files as a data source, aka data packs.

What then are the similarities? Well the same concepts (tile layers, themes, styles, styling rules), architecture, and content organization apply. They both depend on Oracle Spatial for spatial data management and analysis. And while one could define all styles and tile layer definitions dynamically, the ones defined via MapBuilder still work. The table below (from the user guide. Thanks Chuck) lists the correspondence between methods in V1 and V2.

V1 API Class V2 API Class

MVMapView

OM.Map

MVMapTileLayer, MVBingTileLayer, built-in tile layers

OM.layer.TileLayer, OM.layer.BingTileLayer, ElocationTileLayer, NokiaTileLayer, OSMTileLayer

MVCustomMapTileLayer

Custom tile layers are not directly supported in the current release of V2. However, you can use custom tile layers by extending OM.layer.TileLayer and supplying a getTileURL callback function.

MVThemeBasedFOI

OM.layer.VectorLayer

MVFOI

OM.Feature

MVSdoGeometry

OM.geometry and its subclasses

MVEvent

OM.event and its subclasses

MVInfoWindowTab

OM.infowindow.MVInfoWindowTab

Styles (MVStyleColor, MVXMLStyle, MVBucketStyle, MVBarChartStyle, and so on)

OM.style and its subclasses

Tools (MVToolbar, MVDistanceTool, MVCircleTool, and so on)

OM.tool and its subclasses

Decorations and controls (MVNavigationPanel, MVMapDecoration, MVScaleBar, and so on)

OM.control and its subclasses

 The V2 API has the following top-level classes and subpackages, all of which are in the namespace OM:

  • The Map class is the main class of the API.

  • The Feature class represents individual geo features (or FOIs as they were known in V1).

  • The MapContext class a top-level class encapsulating some essential contextual information, such as the current map center point and zoom level. It is typically passed into event listeners.

  • The control package contains all the map controls, such as navigation bar and overview map.

  • The event package contains all the map and layer event classes.

  • The filter package contains all the client-side filters (spatial or relational) for selecting, or subsetting, the displayed vector layer features.

  • The geometry package contains various geometry classes.

  • The layer package contains various tile and vector layer classes. The tile layer classes include access to a few online map services such as Oracle, Nokia, Bing, and OpenStreetMap. The vector layers are interactive feature layers and correspond to the MVThemeBasedFOI and MVFOI classes of V1.

  • The infowindow package contains the customizable information windows and their styles.

  • The style package contains styles applicable to vector data on the client side. It also includes visual effects such as animation, gradients, and drop shadows.

  • The tool package contains various map tools such as for distance measuring, red-lining, and geometry drawing.

  • The universe package contains built-in, or predefined, map universes. A map universe defines the bounding box and set of zoom level definitions for the map content. It is similar to a tile layer configuration in the V1 API.

  • The util package contains various utility classes.

  • The visualfilter package provides an interface for the various visual effects, such as gradients and drop shadows.

OM.Map is the main entry class for all map operations inside the web browser. This and other classes provide interfaces for adding application-specific logic, operations, and interactivity in web mapping applications.

In the next post we'll look at a simple example of using the V2 API.

Friday May 03, 2013

Planned entries for this blog

Over the course of the next few weeks (an entry per week or so) we plan to discuss the new javascript API. After that we'll address Luis Paolini's questions about using mapviewer with database features such as VPD etc.


User Conference info, new features in MapViewer

The Spatial and Graph User Conference will be in DC (Reagan International Center) on May 22nd. The agenda is available at http://www.locationintelligence.net/dc/agenda/.

LJ has two sessions during which he'll talk about some of the new features in MapViewer which include:

  • A new HTML5 Canvas based Javascript API which provides richer interactivity and responsiveness.
  • A web-based spatial data editor. It initially supports 2-d geometry editing (points, lines, polygons, collections) but not planar or network topology. Can be used with Workspace Manager.
  • Access to alternate spatial data sources via GDAL/OGR


Tuesday Apr 09, 2013

Welcome!

Thanks for visiting the official Oracle FMW MapViewer blog.  

 It's been too long since we last blogged about MapViewer (over at our old blog site), but we will try out best to keep this one helpful and active (if not, blame Jayant!)

Stay tuned for more posts about the new MapViewer release, the new HTML5 API and many other new features that we are excited about...

 -LJ 

About

Official blog for Oracle Maps and related products.

Search

Categories
Archives
« July 2016
SunMonTueWedThuFriSat
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
19
20
22
23
24
25
26
27
28
29
30
31
      
Today