Thursday, October 22, 2009

Google maps tiles in a MapViewer app

Earlier this year I posted an article on how to display MapViewer generated tiles in a Google Maps application. In this post I will discuss how to display Google Maps tiles in a MapViewer/Oracle Maps application.

Starting with MapViewer 11g Release 1, a built-in Google Maps tile layer class has been added to the MapViewer's JavaScript API. It is called MVGoogleTileLayer. Unfortunately it is not officially documented for 11g Release 1 (but will be in the upcoming patch and future releases).

This API class is a thin wrapper of the official Google Maps API, and as such you will need to get your own Google Maps key for your application.

The basic steps for displaying Google Maps in your Oracle Maps application are:

Step 1. Import the Google Maps JavaScript library

<script src="http://maps.google.com/maps?file=api&v=2&key=[your-Google-Maps-API-key]"
type="text/javascript"></script>

Again you will need to get your own Google Maps key from Google and set it in the above script tag.

Step 2. Add a Google Maps tile layer to the MVMapView object

mapview = new MVMapView(document.getElementById("map"), baseURL);
baseMap = new MVGoogleTileLayer() ;
mapview.addMapTileLayer(baseMap);

That's all it takes to display a basic Google Maps map in your MapViewer application.

You can also change the type of map you get from Google Maps. Here is an example:

var mapType = G_HYBRID_MAP;
basemap.setMapType(mapType) ;

Where basemap is a MVGoogleTileLayer instance. Note that G_HYBRID_MAP is a constant defined by the imported Google Maps API library and represents a hybrid Google Maps type. The full description for this method is:

setMapType(mapType)
This method sets the type of the map, which can be one of the following predefined Google Map types, G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP and G_PHYSICAL_MAP.

The only other method defined on the MVGoogleTileLayer class is getMapType(), which returns the current map type of the Google Maps being displayed.


Finally, as part of your application cleanup, please call Google Maps' clean-up method GUnload() (such as in the HTML page's unload event listener).

Monday, October 5, 2009

displaying table of longitude and latitude points

While in most cases your location data are stored in an Oracle database as SDO_GEOMETRY type, sometimes you may have certain point data stored simply as two numeric columns in a table (as longitude/latitude for instance). So how can you display these points using MapViewer? In this article I will present one of the more elegant options, namely using function-based index.

The overall steps are:
1. Create a database function that returns a SDO_GEOMETRY object from two numeric values.

2. Treat this function as a SDO_GEOMETRY column by creating an entry in the USER_SDO_GEOM_METADATA view.

3. Create a Spatial index on this function

4. Create a pre-defined theme in Map Builder for this function.

5. Profit.

Lets say we have an existing table CITIES in the schema SCOTT.

SQL> desc cities;
Name Null? Type
------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(100)
ALIASE VARCHAR2(500)
COUNTR VARCHAR2(100)
POP NUMBER
LON NUMBER
LAT NUMBER


Where the two columns LON and LAT represent each city's location.

So the first step is to create a function that generates an actual SDO_GEOMETRY object when supplied with a pair of (lon, lat).

Step 1. Create function

Execute the following while logged in as SCOTT:

create or replace function get_geometry(lon in number,
lat in number)
return SDO_GEOMETRY deterministic is
begin
return sdo_geometry(2001, 8307, sdo_point_type(lon, lat, NULL),NULL, NULL);
end;
/


Note that the function must be deterministic.

Step 2. Populate Spatial metadata view

Now that we have a function, we need to make Spatial and MapViewer treat it as if it's a real SDO_GEOMETRY column. All we need to do is insert an entry for this function in the USER_SDO_GEOM_METADATA view. Again execute the following as SCOTT:

insert into user_sdo_geom_metadata values('CITIES',
'scott.get_geometry(lon,lat)',
sdo_dim_array(
sdo_dim_element('Longitude', -180, 180, 0.005),
sdo_dim_element('Latitude', -90, 90, 0.005)),
8307);

commit;


Step 3. Create a Spatial index:
We now need to create a Spatial index on the function, just like with any SDO_GEOMETRY columns. Without a Spatial index, your SQL spatial queries won't work, and MapViewer cannot display them easily.

create index CITIES_SDX on
CITIES(get_geometry(lon,lat))
indextype is mdsys.spatial_index;


Step 4. Create a Map Builder theme

Open Map Builder, click Show Data to open the data navigator. You should find the CITIES table under the user SCOTT. Right click it and choose "Create Geometry Theme". You should see that the function we just created is already picked up as the Spatial column, as illustrated in this screen shot:



Go through the normal process and complete the theme creation. You now have a pre-defined MapViewer theme that can be used to display those (X,Y) data. You can also add this theme to your AJAX map as a FoI (Feature Of Interest) layer.

Friday, September 18, 2009

Why am I getting "Request string is too long for Oracle Maps' non-AJAX remoting"?

You may get this error when your application is running in a web domain that is different from the one where mapviewer is running, e.g. your application is running on hostA and your MapViewer is running on hostB. Please note that different http ports on the same host are also two different domains.

Oracle Maps client relies on AJAX to exchange data with the MapViewer server. In the scenario described above, when the client needs to send an AJAX request to the Mapviewer server, it is sending the request across domain, which is prohibited by all major web browsers. This is known as the cross-domain constraint.

We implemented our own non-AJAX remoting mechanism to overcome this so that the Oracle Maps client can talk to the MapViewer in a different domain. This workaround has one limitation - the length of the non-AJAX remoting request can not exceed certain limit. If the requests are within the limit, everything will work great. That's why in most cases Oracle Maps application works fine across domain out of box.

But things are not always that simple. If your application displays some complex theme based FOI layers, especially those with JDBC queries and/or dynamic styles, the underlying FOI requests might become too long to be handled by the non-AJAX remoting mechanism. As the result, you will get the error message mentioned in the title.

To solve this problem, you have two options.

Option 1
---------
Deploy a MapViewer to the same domain where your application is running. You don't have to configure this MapViewer to do any real mapping. You'll only need its proxy servlet, which can forward all requests from the client to the remote MapViewer that does the real mapping. The client will send all AJAX requests to the proxy servlet, which will then forward the requests to the remote MapViewer. Because the servlet is in the same domain as the application, there will be no cross-domain constraint when the client talks to the proxy.

In your application code, you need to call MVMapView.enableXMLHTTP(true) at the beginning of your showMap function.

function showMap()
{
MVMapView.enableXMLHTTP(true);
var baseURL = "http://hostB/mapviewer";
mapview = new MVMapView(mapDiv, baseURL) ;
...
}

Option 2
---------
Configure a reverse proxy in your HTTP server to forward requests sent to it to the remote MapViewer. This reverse proxy should accept requests sent to http://hostA/mapviewer/* and forward them to http://hostB/mapviewer/*. If your HTTP server is Apache, then this can be done by adding the following two lines inside mod_proxy section in httpd.conf.

<IfModule mod_proxy.c>
ProxyRequests Off

<Directory proxy:*>
Order deny,allow
Deny from 127.0.0.1
Allow from all
</Directory>

ProxyPass /mapviewer/ http://hostB/mapviewer/
ProxyPassReverse /mapviewer/ http://hostB/mapviewer/

</IfModule>


Your application code should act as if the MapViewer is in the local domain, because the proxy is acting as a local MapViewer instance. For better performance, you should explicitly specify the remote tile server URL when creating a tile layer. By doing so, you're telling the Oracle Maps client to get the map tiles directly from the remote MapViewer, not via the proxy, which also means better performance.

function showMap()
{
var baseURL = "http://"+document.location.host+"/mapviewer" ;
mapview = new MVMapView(mapDiv, baseURL) ;
tileLayer = new MVMapTileLayer("myds.my_map", "http://hostB/mapviewer/mcserver");
...
}


Friday, July 31, 2009

display labels with superscripts and subscripts

My colleague Albert Godfrind just sent me a nice tip on how to display map labels containing subscripts and superscripts. Thought I will share it with you all.

I found the following to work for me. I create a table (using a NVARCHAR2 column for storing the label), then I populate it with two rows: one using the standard label, one using the sub/superscript notation.

create table labels (
id number,
label nvarchar2(30),
geom sdo_geometry
);

insert into labels (id, label, geom)
values (
1,
'm3 CH4 / min',
sdo_geometry (2001, null, sdo_point_type (1,1,null), null, null)
);
insert into labels (id, label, geom)
values (
2,
unistr('m\00B3 CH\2084 / min'),
sdo_geometry (2001, null, sdo_point_type (1,2,null), null, null)
);
commit;

insert into user_sdo_geom_metadata values (
'LABELS',
'GEOM',
sdo_dim_array (
sdo_dim_element ('x', 0, 100, 0.05),
sdo_dim_element ('y', 0, 100, 0.05)
),
null
);
commit;
create index labels_sx on labels (geom) indextype is mdsys.spatial_index;

Mapviewer then nicely renders the super/sub scripts as illustrated!

Tuesday, July 28, 2009

Java API: setting array-type binding variables for a theme

MapViewer supports pre-defined themes that contain query conditions with one or more binding variables. For instance, we can define a theme COUNTIES_BY_STATES that displays all the counties whose State abbreviations are in a list supplied by user at run time.

The query condition for this theme may look like the following:

( state_abrv in (select column_value from table(:1)) )


In other words, when this theme is to be displayed, it will require a list of State abbreviations such as 'CA', 'WA', 'NY' as the value for its query's binding variable. Note that this binding variable itself requires a SQL TABLE type.

So how do you supply such an array of state abbreviation codes to the MapViewer from your Java application? The following shows the sample code that does just that.

String [] values = { "MA","NY","ME","NH" }; //list of state abbreviations
ArrayParameter states = new ArrayParameter("MV_STRINGLIST",values);
Object [] values = new Object[1]; //array containing all binding variable values
values[0] = states;
mapViewer.setThemeBindingParameters("COUNTIES_BY_STATES", values);

Note that basically you must use MapViewer's public ArrayParameter class to supply the list of states (or list of any strings/numbers) for a single table-type binding variable. Because in this case there is only one binding variable in this theme's query, the values array contains only one element, the ArrayParameter instance.

Note also MV_STRINGLIST is a SQL list/table type created by MapViewer automatically in each data source (database schema). Please check the 11g MapViewer user's guide on more information about this pre-created type.

multi-line labels

MapViewer supports labels or annotations that contain a newline char (ASCII char code 10). However by default such characters are ignored by MapViewer during the map text labeling process, and the label text appears in a single line (unless there is a text wrap width specified.)

To force MapViewer to honor the newline chars in a theme's label texts, the theme must use a TEXT labeling style with the "Honor Newline" attribute set to true. This can be done from MapBuilder's TEXT style editor, by simply selecting the Honor newline check box. Now any theme that uses this TEXT style will automatically display any label text containing newline characters with multiple lines.

If your application is written using the MapViewer Java API, and you need to add individual geo-features with multi-line text labels to your map, then additional caution must be taken when constructing the label text with the newline char. Namely, the label text for a feature must be constructed like this:

String textLabel = "LINE 1" + "%26#10;" + "LINE 2";

Typically in an XML document (the Java API eventually will convert your current map request into an XML request before sending it to the MapViewer server), one uses &#10; to represent the newline char(10). Because we are sending the entire map request string using HTTP POST from map client to the server, the character '&' itself must be escaped as '%26', hence the odd-looking string %26#10; representing an HTTP-safe newline character.

So this is how you can construct a new feature with this text as its label:

mapViewer.addPointFeature(centreX, centreY, 8265, "C.RED", textLabel, "MY_TEXT_STYLE",
null, null, true);

where MY_TEXT_STYLE should be a TEXT type style with "Honor newline" enabled.

Monday, July 6, 2009

MapViewer 11g R1 production kit released

We are excited to announce that as part of Oracle's Fusion Middleware 11g R1 release, MapViewer 11g R1 is now available for download! Please navigate to OTN MapViewer home page here: http://www.oracle.com/technology/products/mapviewer/index.html. The 11g R1 software can be found under the Software link on the right side of the page.

Note that this release includes all the features and bug fixes of MapViewer patch 10.1.3.3 for WebLogic server, and we strongly encourage users to upgrade to the 11g R1 release to take advantage of the many new enhancements and a more stable Oracle Maps JavaScript API.

It supports not only WebLogic server (and Oracle AS/OC4J 10.1.3.x), but also Tomcat 6.x and JBoss 4.x. The full certification matrix can be found here:
http://www.oracle.com/technology/products/mapviewer/htdocs/j2ee_server_support.html

For the complete list of all the new and enchanced features of MapViewer 11g R1, please check out the New and Changed section of the MapViewer User's Guide, also downloadable from the OTN MapViewer home.