Monday, March 9, 2009

Displaying MapViewer tile layers over Google Maps

A lot of folks have asked how to display a MapViewer tile layer in a Google Maps application. My colleague Jayant Sharma recently wrote a very nice note on this subject, using Google Maps' custom tile layer support. The following are the detailed instructions form his note. They can also be used with other map services such as Microsoft Virtual Earth.

Overview


There are 3 high-level steps involved:
  1. Preparing your Oracle Spatial data by creating the EPSG:3785 coordinate system and transforming the data.
  2. Creating a MapViewer tile layer for your data.
  3. Displaying the MapViewer tile layer over Google Maps using its custom tile layer mechanism.



Adding the EPSG:3785 coordinate system definition

The Google Maps are projected and rendered in a coordinate system known as Spherical Mercator (EPSG id 3785). So the data stored in an Oracle database and rendered by MapViewer into map tiles must also be in that coordinate system. This section describes the steps to add this new EPSG coordinate system definition into an Oracle database.

Note: This requires a 10.2.0.4 database. Earlier versions do not have the required patch that enables transformations (SDO_CS.TRANSFORM) between this (3785) and other (e.g. WGS84 or BNG) coordinate systems.


Note 2: You may want to verify that the SRID 3785 does NOT already exist in the database. To verify, you can run the SQL query "select srid from mdsys.cs_srs where srid=3785" from any database user. If a row is returned, then you already have this SRID, and you should SKIP this section. With the latest 11g Oracle database this SRID does indeed exist out of the box.


Connect to your 10.2.0.4 database instance (with Spatial or Locator installed and enabled) as a privileged user (e.g. a user with the DBA role) and issue the following SQL statements.


insert into MDSYS.SDO_ELLIPSOIDS (

ELLIPSOID_ID,

ELLIPSOID_NAME,

SEMI_MAJOR_AXIS,

UOM_ID,

INV_FLATTENING,

SEMI_MINOR_AXIS,

INFORMATION_SOURCE,

DATA_SOURCE,

IS_LEGACY,

LEGACY_CODE)

VALUES (

7059,

'Popular Visualisation Sphere',

6378137,

9001,

1.0000E+12,

NULL,

null,

'EPSG',

'FALSE',

null);


insert into MDSYS.SDO_DATUMS (

DATUM_ID,

DATUM_NAME,

DATUM_TYPE,

ELLIPSOID_ID,

PRIME_MERIDIAN_ID,

INFORMATION_SOURCE,

DATA_SOURCE,

SHIFT_X,

SHIFT_Y,

SHIFT_Z,

ROTATE_X,

ROTATE_Y,

ROTATE_Z,

SCALE_ADJUST,

IS_LEGACY,

LEGACY_CODE)

VALUES (

6055,

'Popular Visualisation Datum',

'GEODETIC',

7059,

8901,

null,

'EPSG',

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

'FALSE',

NULL);


insert into MDSYS.SDO_COORD_REF_SYSTEM (

SRID,

COORD_REF_SYS_NAME,

COORD_REF_SYS_KIND,

COORD_SYS_ID,

DATUM_ID,

geog_crs_datum_id,

SOURCE_GEOG_SRID,

PROJECTION_CONV_ID,

CMPD_HORIZ_SRID,

CMPD_VERT_SRID,

INFORMATION_SOURCE,

DATA_SOURCE,

IS_LEGACY,

LEGACY_CODE,

LEGACY_WKTEXT,

LEGACY_CS_BOUNDS,

is_valid,

supports_sdo_geometry)

VALUES (

4055,

'Popular Visualisation CRS',

'GEOGRAPHIC2D',

6422,

6055,

6055,

NULL,

NULL,

NULL,

NULL,

null,

'EPSG',

'FALSE',

NULL,

NULL,

NULL,

'TRUE',

'TRUE');


INSERT INTO sdo_coord_ops (

coord_op_id,

coord_op_name,

coord_op_type,

source_srid,

target_srid,

coord_tfm_version,

coord_op_variant,

coord_op_method_id,

UOM_ID_SOURCE_OFFSETS,

UOM_ID_TARGET_OFFSETS,

information_source,

data_source,

show_operation,

is_legacy,

legacy_code,

reverse_op,

is_implemented_forward,

is_implemented_reverse)

VALUES (

19847,

'Popular Visualisation Mercator',

'CONVERSION',

null,

null,

'',

null,

9804,

null,

null,

null,

null,

1,

'FALSE',

null,

1,

1,

1);


insert into MDSYS.SDO_COORD_OP_PARAM_VALS (

COORD_OP_ID,

COORD_OP_METHOD_ID,

PARAMETER_ID,

PARAMETER_VALUE,

PARAM_VALUE_FILE_REF,

UOM_ID)

VALUES (

19847,

9804,

8801, -- Latitude of natural origin

0,

NULL,

9102);


insert into MDSYS.SDO_COORD_OP_PARAM_VALS (

COORD_OP_ID,

COORD_OP_METHOD_ID,

PARAMETER_ID,

PARAMETER_VALUE,

PARAM_VALUE_FILE_REF,

UOM_ID)

VALUES (

19847,

9804,

8802, -- longitude of natural origin

0,

NULL,

9102);


insert into MDSYS.SDO_COORD_OP_PARAM_VALS (

COORD_OP_ID,

COORD_OP_METHOD_ID,

PARAMETER_ID,

PARAMETER_VALUE,

PARAM_VALUE_FILE_REF,

UOM_ID)

VALUES (

19847,

9804,

8805, -- scale factor at natural origin

1,

NULL,

9201);


insert into MDSYS.SDO_COORD_OP_PARAM_VALS (

COORD_OP_ID,

COORD_OP_METHOD_ID,

PARAMETER_ID,

PARAMETER_VALUE,

PARAM_VALUE_FILE_REF,

UOM_ID)

VALUES (

19847,

9804,

8806, -- false easting

0,

NULL,

9001);


insert into MDSYS.SDO_COORD_OP_PARAM_VALS (

COORD_OP_ID,

COORD_OP_METHOD_ID,

PARAMETER_ID,

PARAMETER_VALUE,

PARAM_VALUE_FILE_REF,

UOM_ID)

VALUES (

19847,

9804,

8807, -- false northing

0,

NULL,

9001);


INSERT INTO sdo_coord_ref_system (

srid,

coord_ref_sys_name,

coord_ref_sys_kind,

coord_sys_id,

datum_id,

geog_crs_datum_id,

source_geog_srid,

projection_conv_id,

cmpd_horiz_srid,

cmpd_vert_srid,

information_source,

data_source,

is_legacy,

legacy_code,

legacy_wktext,

legacy_cs_bounds,

is_valid,

supports_sdo_geometry)

VALUES (

3785,

'Popular Visualisation CRS / Mercator',

'PROJECTED',

4499,

null,

6055,

4055,

19847,

Null,

Null,

null,

null,

'FALSE',

null,

null,

null,

'TRUE',

'TRUE');


-- create the tfm_plans, i.e. transformation rule.

-- Note: This will result in an incorrect conversion since it ignores a datum shift

-- between the ellipsoid and the sphere. However the data will match up better

-- on google maps

-- first for wgs84 (8307)


call sdo_cs.create_pref_concatenated_op( 83073785, 'CONCATENATED OPERATION 8307 3785', TFM_PLAN(SDO_TFM_CHAIN(8307, 1000000000, 4055, 19847, 3785)), NULL);


-- 4326 is the EPSG equivalent of 8307

call sdo_cs.create_pref_concatenated_op( 43263785, 'CONCATENATED_OPERATION_4326_3785', TFM_PLAN(SDO_TFM_CHAIN(4326, 1000000000, 4055, 19847, 3785)), NULL);


-- similarly for os bng (oracle srid 81989 or epsg 27700 it is

call sdo_cs.create_pref_concatenated_op( 819893785, 'CONCATENATED OPERATION 81989 3785', TFM_PLAN(SDO_TFM_CHAIN(81989, -19916, 2000021, 1000000000, 4055, 19847, 3785)), NULL);


-- 27700 is the EPSG equivalent of 81989

call sdo_cs.create_pref_concatenated_op( 277003785, 'CONCATENATED_OPERATION_27700_3785', TFM_PLAN(SDO_TFM_CHAIN(27700, -19916, 4277, 1000000000, 4055, 19847, 3785)), NULL);

commit;


Next transform existing data, or a copy of it, into 3785 and use it for generating the tiles. Otherwise the data will be transformed on the fly and that’ll slow down tile generation. The next section will use the MVDEMO sample data set to illustrate the steps.




Setting up a Oracle map tile layer for Google Maps


The MVDEMO sample dataset has tables named counties, cities, interstates, and states and the data is in WGS84 (srid = 8307). There’s also a set of styles, themes, basemaps, and map tile caches defined. These definitions are stored in the USER_SDO_STYLES, SUER_SDO_THEMES, USER_SDO_MAPS, and USER_SDO_CACHED_MAPS views.


The first step is to create a set of tables with the data transformed to 3785.


create table states_3785 as select * from states;

create table interstates_3785 as select * from interstates;

create table cities_3785 as select * from cities;

create table counties_3785 as select * from counties;


update states_3785 set geom = sdo_cs.transform(geom, 3785);

update interstates_3785 set geom = sdo_cs.transform(geom, 3785);

update cities_3785 set location = sdo_cs.transform(location, 3785);

update counties_3785 set geom = sdo_cs.transform(geom, 3785);

commit;


-- insert metadata

insert into user_sdo_geom_metadata values('STATES_3785', 'GEOM',

sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);


insert into user_sdo_geom_metadata values('INTERSTATES_3785', 'GEOM',

sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);


insert into user_sdo_geom_metadata values('COUNTIES_3785', 'GEOM',

sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);


insert into user_sdo_geom_metadata values('CITIES_3785', 'LOCATION',

sdo_dim_array(sdo_dim_element('X', -20037508.3427, 20037508.3427, 0.05), sdo_dim_element('Y', -20037508.3427, 20037508.3427, 0.05)), 3785);


-- create spatial indexes

create index states_3785_sidx on states_3785(geom) indextype is mdsys.spatial_index;

create index interstates_3785_sidx on interstates_3785(geom)

indextype is mdsys.spatial_index;

create index counties_3785_sidx on counties_3785(geom)

indextype is mdsys.spatial_index;

create index cities_3785_sidx on cities_3785(location)

indextype is mdsys.spatial_index parameters(‘layer_gtype=point’);


The second step is to create the themes for these tables and after that a basemap made of these newly created themes. This can be done with MapBuilder or using SQL statements to copy the theme definitions while changing the referenced table names by appending a _3785 to the theme and table name.


insert into user_sdo_themes select name || ‘_3785’, description,

base_table || ‘_3785’, geometry_column, styling_rules

from user_sdo_themes where base_table in (‘STATES’, ‘INTERSTATES’,

‘COUNTIES’, ‘CITIES’) and length(name) < 27


commit;


-- if a theme name is > 27 chars long modify it individually

-- It’ll has to be manually modified in the basemap definition

-- too. E.g there may be one name new_theme_demo_highways_line

-- that can be named theme_demo_highways_line_3785 instead


Next define a basemap using these themes. You can use the following SQL statements to manually create a new basemap named DEMO_MAP_3785 that is similar to DEMO_MAP, then update its referenced theme names and map scales:


insert into user_sdo_maps select ‘DEMO_MAP_3785’, description,definition

from user_sdo_maps where name=’DEMO_MAP’;

update user_sdo_maps set definition=
'<?xml version="1.0" standalone="yes"?>
<map_definition>
<theme name="THEME_DEMO_STATES_3785" />
<theme name="THEME_DEMO_COUNTIES_3785" min_scale="3000000.0" max_scale="0.0" scale_mode="RATIO"/>
<theme name="THEME_DEMO_STATES_LINE_3785" min_scale="3000000.0" max_scale="0.0" scale_mode="RATIO"/>
<theme name="THEME_DEMO_HIGHWAYS_3785"/>
<theme name="THEME_DEMO_CITIES_3785" min_scale="6000000.0" max_scale="0.0" scale_mode="RATIO"/>
<theme name="THEME_DEMO_BIGCITIES_3785"/>
</map_definition>'
where name='DEMO_MAP_3785';
commit;


The third step is to create a map tile layer. Navigate to MapViewer’s Admin console (i.e. go to http://localhost:8888/mapviewer/ , click on the Admin button, log in as a oc4jadmin) and then on the Manage Map Tile Layers tab. Then on Create link in the left panel and then the Continue button. Name the map tile layer, e.g. demo_map_3785, choose mvdemo as the data source and demo_map_3785 as the basemap. Check the transparent checkbox next to the Background color selection box. The default color will be ignored and the map tiles will be rendered on a transparent background. Then set # Zoom Levels to 20 (the number used by Google Maps), ignore the scale level settings, SRID to 3785, Min X to -20037508.3427, Max X to 20037508.3427, Min Y to -20037508.3427, and Max Y to 20037508.3427. Leave the tile size as 256 by 256 and format as PNG. Do NOT click submit. Click XML Mode instead.

To workaround a bug in the UI, which does not let you enter specific tile sizes, we will manually enter this information in XML mode. So click on XML mode and copy and paste the following XML fragment over the existing <zoom levels> … </zoom_levels> fragment. (Note: the following values have been revised and are correct as of April 09 2009)


<zoom_levels levels="20" min_scale="0.0" max_scale="0.0" min_tile_width="76.43702697753906" min_tile_height="4.0075016E7">
<zoom_level level="0" name="" description="" scale="0.0" tile_width="4.0075016E7" tile_height="4.0075016"/>
<zoom_level level="1" name="" description="" scale="0.0" tile_width="2.0037508E7" tile_height="2.0037508E7"/>
<zoom_level level="2" name="" description="" scale="0.0" tile_width="1.0018754E7" tile_height="1.0018754E7"/>
<zoom_level level="3" name="" description="" scale="0.0" tile_width="5009377.0" tile_height="5009377.0"/>
<zoom_level level="4" name="" description="" scale="0.0" tile_width="2504688.5" tile_height="2504688.5"/>
<zoom_level level="5" name="" description="" scale="0.0" tile_width="1252344.25" tile_height="1252344.25"/>
<zoom_level level="6" name="" description="" scale="0.0" tile_width="626172.125" tile_height="626172.125"/>
<zoom_level level="7" name="" description="" scale="0.0" tile_width="313086.0625" tile_height="313086.0625"/>
<zoom_level level="8" name="" description="" scale="0.0" tile_width="156543.03125" tile_height="156543.03125"/>
<zoom_level level="9" name="" description="" scale="0.0" tile_width="78271.515625" tile_height="78271.515625"/>
<zoom_level level="10" name="" description="" scale="0.0" tile_width="39135.7578125" tile_height="39135.7578125"/>
<zoom_level level="11" name="" description="" scale="0.0" tile_width="19567.87890625" tile_height="19567.87890625"/>
<zoom_level level="12" name="" description="" scale="0.0" tile_width="9783.939453125" tile_height="9783.939453125"/>
<zoom_level level="13" name="" description="" scale="0.0" tile_width="4891.9697265625" tile_height="4891.9697265625"/>
<zoom_level level="14" name="" description="" scale="0.0" tile_width="2445.98486328125" tile_height="2445.98486328125"/>
<zoom_level level="15" name="" description="" scale="0.0" tile_width="1222.992431640625" tile_height="1222.992431640625"/>
<zoom_level level="16" name="" description="" scale="0.0" tile_width="611.4962158203125" tile_height="611.4962158203125"/>
<zoom_level level="17" name="" description="" scale="0.0" tile_width="305.74810791015625" tile_height="305.74810791015625"/>
<zoom_level level="18" name="" description="" scale="0.0" tile_width="152.87405395507812" tile_height="152.87405395507812"/>
<zoom_level level="19" name="" description="" scale="0.0" tile_width="76.43702697753906" tile_height="76.43702697753906"/>
</zoom_levels>



Now click the Submit button.

Click on the Manage link and you should see the Existing Map Tile Layers list. Select demo_map_3785 and click on View Map / Manage Tiles. Enter –12000000 for Center X, and 5000000 for Center Y and Show Map. Or just click show map and pan to the left. You should see tiles being generated and displayed. Click return. Below is a screenshot of this new tile layer:



The actual display code


The following sample code uses the tile served up by MapViewer in a Google Maps application. You should save it as tileoverlay-simple.html.



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Google Maps JavaScript API Example: Tile Overlays</title>
<script src="http://maps.google.com/maps?file=api&v=2&key=ABQIAAAA9WYjhAcvlemZrWK0oPA3ZBSN0IHl8d7gGlihtYuSKk78QOdoAhRRFH3M5iCXxdFN-KENpX8NdJwm0Q"
type="text/javascript"></script>
<script type="text/javascript">
function initialize() {
if (GBrowserIsCompatible()) {

// Set up the copyright information
// Each image used should indicate its copyright permissions
var myCopyright = new GCopyrightCollection("(c) ");
myCopyright.addCopyright(new GCopyright('Demo',
new GLatLngBounds(new GLatLng(-90,-180),
new GLatLng(90,180)), 0,'?2007 Google'));

// Create the tile layer overlay and
// implement the three abstract methods
var tilelayer = new GTileLayer(myCopyright);

tilelayer.getTileUrl = function(tile, zoom) {
var req=null;
req = "http://stadb32.us.oracle.com:7001/mapviewer/mcserver?" +
"request=gettile&format=PNG&"+
"zoomlevel="+(zoom)+"&mapcache=mvdemo.demo_map_3785&mx="
+(tile.x)+"&my="+(Math.pow(2,zoom)-1-tile.y)+"&";
return req;};

tilelayer.isPng = function() { return true;};
tilelayer.getOpacity = function() { return 0.75; }

var myTileLayer = new GTileLayerOverlay(tilelayer);
var map = new GMap2(document.getElementById("map_canvas"));
map.setCenter(new GLatLng(33.4419, -90.1419), 4);
map.addControl(new GSmallMapControl());
map.addOverlay(myTileLayer);
}
}
</script>
</head>
<body onload="initialize()" onunload="GUnload()">
<div id="map_canvas" style="width: 800px; height: 600px"></div>
</body>
</html>



The relevant (modified) code is the getTileUrl function. Google Maps passes in the tile coordinates for the requested tile and the current zoom level. The parameter “tile” is a GPoint object with “x” and “y” attributes. So tile.x is the x-coordinate for the tile. This is same for Oracle Maps since it also goes from left to right. The y-coordinate value however needs to be recomputed since Google’s Y values increase top to bottom while Oracle’s increase bottom to top. In both cases there are 2Nx2N tiles at zoom level N. So the Y value for the Oracle Maps tile will be (2N –1 – tile.y).


You should modify the Google Maps key as well as the MapViewer server URL in the above code. Save the code as tileoverlay-simple.html, then open it from a browser, and you should see a Google Maps map with the MapViewer tile layer overlay.




Final note, while this article presented how to display MapViewer tiles in a Google Maps mash-up application, the up-coming release of MapViewer (11g R1) will include built-in support for displaying Google Maps and Microsoft Virtual Earth tiles from within your Oracle Maps application. A blog about such support will be posted here as soon as MapViewer 11g R1 is released (which hopefully is just around the corner).

Monday, January 26, 2009

Heat map support in MapViewer 11g

One of the many new features of Oracle MapViewer 11g (which is now in the final testing stage) is heat maps. If you don't know what exactly is a heat map, it is a color based 2D representation of a point data set's distribution pattern. It is great at revealing clusters of points or events that occur in a study region.

Here is a screen shot of such a heat map fully integrated with the Oracle Maps JavaScript API.



The above map displays the CITIES table in MapViewer's MVDEMO data set as a heat map. The color gradients (from white to green to orange and finally to red) shows the distribution pattern (clusters) of cities and towns of US. In MapViewer terms, the CITIES theme is rendered using a HEAT MAP style. Internal tests have shown MapViewer can easily generate a heat map from hundreds of thousands of points, on the fly.

Here is another screen shot, this time showing the distribution of pizza restaurants across the states.

Wednesday, January 21, 2009

Creating a separate MapViewer admin account

By default MapViewer borrows the same admin account that you use to log into the Application Server admin web page. For OracleAS 10.1.3.* this is typically the user named "oc4jadmin", and for WebLogic it's typically "weblogic".

In some cases however you may want to create a separate admin account just for MapViewer,such as when you do not wish to give away the middleware's master admin account information. Basically, this new account can only be used to log into MapViewer's admin page and perform all of its management tasks. It cannot be used to log into the middleware's master admin web site (the /em URL for OracleAS 10.1.3.* or the /console URL for WebLogic Server).

So how do you do that? We will show you the steps for both OracleAS 10.1.3 and WebLogic Server 10.

Oracle Application Server 10.1.3.*

Lets first assume you already have MapViewer deployed to one of the OC4J instances in your OracleAS 10.1.3.* installation. If MapViewer is not deployed yet, you should go ahead and deploy it.

Step 1. Log onto App Server's admin web site (/em).
Step 2. Find the OC4J instance that contains MapViewer. Then click on its Administration tab.
Step 3. Find the Security - Security Providers row, then click the Go to Task icon. This is shown in the following screen shot.



Step 4. Click the "Instance Level Security" button
Step 5. Select the Realms tab
Step 6. Click the number in the Users column of the row starting with "jazn.com".
Step 7. Click the Create button.
Step 8. Enter the user name and password for the new user to be created for MapViewer. For example I entered mapadmin as the user name.
Step 9. On the same page, from Available Roles, select and move "oc4j-app-administrators" to the Selected Roles box. Click Ok. You should see a confirmation page showing the new user and its assigned roles (oc4j-app-administrators).

The above steps create a new admin user for MapViewer with the assigned role of oc4j-app-administrators. We now need to modify Mapviewer's deployment descriptor so that it will accept this role for administration purposes.

Modify MapViewer's deployment descriptor

Before performing the following step, I typically shutdown the entire OracleAS instance; however it may also work if you just shut down the OC4J instance that contains MapViewer.

Once the OC4J instance is down, edit the following MapViewer deployment descriptor file:

AS_HOME/j2ee/home/application-deployments/mapviewer/web/orion-web.xml

where AS_HOME is the home directory of your OracleAS install. If your MapViewer is NOT deployed to the "home" OC4J instance, then you will also need to replace "home" with the directory name of the actual OC4J instance containing MapViewer. If your OC4J is named "mapper" for instance, then the direcorry is typically AS_HOME/j2ee/oc4j_mapper/...

Once the orion-web.xml file is opened in an editor, locate the XML element <security-role-mapping name="map_admin_role">. Add a new child <group> element
<group name="oc4j-app-administrators">. In other words, the entire element should look like this:

<security-role-mapping name="map_admin_role">
<group name="oc4j-app-administrators"/>
<group name="oc4j-administrators"/>
<group name="administrators"/>
</security-role-mapping>


You may also find another tag <security-role-mapping name="secure_maps_role"> in the same file, leave that one alone (it is used for the secure mapping demos).

By making the above change, we are telling MapViewer that any App Server user with the role "oc4j-app-administrators" should also be considered a MapViewer admin user.

Save the file. And restart OC4J or OracleAS instance.

Now you should be able to log into MapViewer's admin page using the newly created user (mapadmin in my case). All the admin tasks of MapViewer should go through as usual. You will not be able to log into the App Server admin site (/em) using mapadmin. You can however, still log into MapViewer's admin page using the oc4jadmin account (after all it is the master admin for the entire OracleAS instance).

WebLogic Server

To be added later...

Monday, November 24, 2008

Official MapViewer white paper: a primer

Just FYI the official MapViewer Primer article can be found here:

http://www.oracle.com/technology/products/mapviewer/mapviewer_training_index.html

It provides a good and concise introduction on MapViewer.

Wednesday, November 19, 2008

Creating a web map from raster image files

This article outlines the steps needed to create a "slippy" web map from a set of raster image files on your disk. The main steps are:

  1. load the image files into Oracle Spatial and store them as a GeoRaster table

  2. create a MapViewer theme and base map for the GeoRaster table

  3. create a MapViewer map tile layer so that the AJAX mapping client can display the data as a slippy map in the browser


Lets now go through these steps with a bit more detail.

Loading image files into Oracle Spatial

For this we will use the Map Builder tool. If you don't have the latest version of Map Builder, you can get it from OTN MapViewer page. The latest kit is MapViewer version 10.1.3.3 for WebLogic.

Fire up Map Builder, then go to Tools > Import Image. You will be prompted to enter the name for a GeoRaster table to be created (if you are importing an image for the first time), and a few other things including the raster data table name and georaster column name. You should jot down these names as you will need to enter them again when importing a second image and so forth.



Click Next. You can accept default values for all the fields. Note that the SRID for the georaster data is set to 999999 by default, you should change it if you know the exact Oracle SRID for the image files you are importing. If you do not know the SRID, you should set it to 262148 here. SRID 262148 is a meter-based local coordinate system, and will ensure you can create a tile layer in the last step. Note that you can always update it later to a more accurate SRID.



Click Next. Then click Finish. Map Builder will start uploading the image file to the database.




After the image is uploaded and stored in the GeoRaster table, Map Builder will automatically prompt you to create a GeoRaster theme based on the table. A GeoRaster theme is required by MapViewer to visualize your image data. So click Next if this is the first image you just uploaded and there is no GeoRaster theme exists for it.




Enter a name for your new GeoRaster theme, then click Next. Click Next also on the 2nd and 3rd screen of this dialog. Then click Finish. You now have a GeoRaster theme for the image you just uploaded. You can preview this theme in Map Builder to ensure everything so far is working (in other words, you should see your image which is now stored in the database).


If you have more than one image files, just repeat the above steps. Note that you should use the same GeoRaster table name, column name and Raster Data Table name for all subsequent image files. This will ensure that all the image files are stored in the same GeoRaster table in Oracle Spatial. It will also make sure Map Viewer automatically "stitch" your image files when visualizing them. After finishing uploading each image file, MapBuilder will prompt again and ask if you want to create a GeoRaster theme. Click Cancel here since you already created the theme, which will automatically see new image data added to the GeoRaster table.


Creating a MapViewer base map

Now that all of your image files are stored neatly as a GeoRaster table in Oracle Spatial, and MapViewer is (partially) happy because you created a theme based on that table, there remains two more steps for the data to be displayable in a "slippy" map in your browser. First, we need to create a MapViewer Base map that includes/references this theme. Second we need to create a map tile layer which is what the slippy mapping client is expecting.

To create a base map, we will again use Map Builder. Right click on the Base Maps node in the left panel of Map Builder. In the ensuing dialog, enter a name for the new base map. Click Next. In the second screen as shown below, find and select the theme we just created, and click the button with a "+" icon to add the theme to the new base map.





Click Next and Finish. This will create a MapViewer base map that contains just the GeoRaster theme. Again you should be able to preview the base map inside Map Builder and see the same image data.

Creating a Map Tile Layer


The slippy Oracle Maps map cannot directly display a MapViewer base map for legacy reasons. It expects a MapViewer tile layer instead. So we need to create a map tile layer out of the base map. For this you will need to use the MapViewer Admin web page. So point your browser to the MapViewer home page, click the Admin icon/button, and login to the Admin page. Click the Management tab, then Manage Map Tile Layers. Click Create, select Internal. Click Continue. And you will see a web page that defines all aspects of the new Tile Layer, such as its name, what is the base map, and so forth. This is illustrated in the following screen shot:





Note that you will need to make sure proper values are entered for the fields Min X, Min Y, Max X, Max Y, Minimum Map Scale, Max Map Scale and SRID. The fields Min X, Min Y, Max X and Max Y define the coordinates of an imaginary bounding box that covers all the imported images. You should be able to get these values by checking the coordinates in the TFW files. Or you can use Map Builder's preview feature to get a rough estimate since the preview panel displays the coordinate info too.

Min and Max Map Scale define the smallest and largest map scales that the map images should be displayed (or will display reasonablly well in terms of clarity). They are true map scales and you can experiment with different values to get a sense of how they affect the user experience. A value of 1000 means the map scale is 1:1000, which in turn means a unit of distance (say an inch) on the screen represents 1000 same units (inches) on the ground.

The other field, # Zoom Levels, defines how many steps to go from zoomed way out (Max Map Scale) to way in (Min Map Scale). Again, it is okay to experiment with all of these values in case you didnt get it all right the first time. It's not a big deal to delete the current one and start over with a new map tile layer.

Finally, the SRID field should contain the same SRID value you entered when importing images in Map Builder.

Testing your map


So how do you actually know if your map tile layer is created properly? You display it in a simple HTML page! The easiest way is to copy and modify one of the existing Oracle Maps tutorials (each tutorial is one very simple HTML/JavaScript page). Simply replace the map tile name with the one you just created, and set the proper map center point and zoom level for your data. Open the page in your browser and you should see a draggable slippy map showing your image data!

Tuesday, November 4, 2008

draggable magnifying glass on your map

My colleague Ji Yang recently developed some neat code to implement a movable magnifying glass or "microscope" that reveals more details on any spot on the map, without zooming into the map itself.

You can download the simple HTML page that implements this effect here. And here is the icon referenced in the page. The demo works off the sample MVDEMO data set.

The code simply uses the MVMapDecoration and other standard Oracle Maps JavaScript API. The basic idea is to create a duplicate set of MVMapView handle and theme-based FOI layers for the DIV container that serves as the microscope window. Then add this window as a movable Map Decoration object to the main map. The map and themes in the microscope window show things at a different zoom level from the main map to achieve the "amplifying" effect.

Anyway, here is a quick screen shot with the magnifying glass spotlighting on two different locations on the same map.

Wednesday, October 29, 2008

Generating map legends

In this article we will show at least 3 different ways of generating a map legend. The first two are based on MapViewer's XML API, while the 3rd one is pure HTML and the most flexible. All the examples have been tested against the sample MVDEMO data source.

Approach 1: manually specifying legend entries

In this approach, we create a map legend image by specifying a map legend element in MapViewer's XML request. Inside the element, we add one legend entry for each style to be displayed as well as some description text. The trick here is to leave MapViewer nothing else to render: no center point, no map bounding box, and no base map or themes either. When MapViewer sees such an otherwise empty map request, it will know to generate an image that is just big enough to contain all the legend entries.

The following is one such sample XML request.

<?xml version="1.0" standalone="yes"?>
<map_request datasource="mvdemo" format="PNG_STREAM">
<legend bgstyle="fill:#ffffff;stroke:#ff0000" profile="MEDIUM" position="SOUTH_EAST">
<column>
<entry text="Map Legend" is_title="true" />
<entry style="M.STAR" text="center point" />
<entry style="M.CITY HALL 3" text="cities" />
<entry style="M.CITY HALL 4" text="big cities" />
<entry style="C.ROSY BROWN STROKE" text="state boundary" />
<entry style="L.PH" text="interstate highway" />
<entry style="L.FERRY" text="ferry" />
<entry is_separator="true" />
<entry text="County population:" />
<entry style="V.COUNTY_POP_DENSITY" tab="1" />
</column>
</legend>

</map_request>

The above request results the following legend image:



Approach 2: Fully automatic embedded map legend

In this approach, we want to display a map legend as part of the map itself, while without specifying any legend entries. Lets look at the such a map request:

<?xml version="1.0" standalone="yes"?>
<map_request
title="Oracle LBS MAP"
basemap="demo_map"
datasource = "mvdemo"
width="640"
height="480"
bgcolor="#a6cae0"
antialiase="false"
format="PNG_STREAM">
<center size="0.15">
<geoFeature render_style="m.star"
radius="1600,4800"
label="A Place"
text_style="t.Street Name" >
<geometricProperty typeName="center">
<Point srsName="SDO:8307">
<coordinates>-122.2615, 37.5266</coordinates>
</Point>
</geometricProperty>
</geoFeature>
</center>

<legend bgstyle="fill:#ffffff;stroke:#ff0000" profile="MEDIUM" position="SOUTH_EAST">
</legend>

</map_request>

As you can see, the above request specifies a legend element without any actual legend entries inside it. So how will MapViewer create the legend in this case? It actually will look up all the themes that are visible on the result map, and displays all the styles that are used by these themes in the legend area. This is great if you are just feeling lazy and would rather MapViewer handle everything for you. The following is the map with the automatic legend:



For more information about these two approaches and the (sometimes limited) customization options, please check out MapViewer's User Guide.


Approach 3: Full manual mode


If you are an advanced user, you most likely will want more than the two approaches we just mentioned. No worries. In the 3rd approach, we will leverage a new MapViewer request to achieve complete control of legend customization and layout. Basically, with a recent MapViewer release (such as 10.1.3.3 for WebLogic), you can now request a 'sample' image of any style from Mapviewer. A typical request URL for a style image looks like this:

http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=M.STAR&w=25&h=25&ds=mvdemo

For the above URL, MapViewer will generate and stream back an image 25 pixels wide and high that shows the style named "M.STAR" from the "mvdemo" data source.

With this kind of style image URL, we can use the regular HTML table and <img> tags to fully customize what, where and how legend entries are displayed. For instance, to replicate the sample legend in Approach 1, we can use the following simple HTML table (you can also use CSS constructs to achieve much fancier layout if you so desire):


<TABLE>
<TR>
<TD>
<Font size="+1" color="blue">Basic Features:</FONT>
</TD>
<TD>
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=M.STAR&w=25&h=25&ds=elocation">
</TD>
<TD>
map center
<TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=M.CITY+HALL+3&w=25&h=25&ds=mvdemo">
</TD>
<TD>
cities
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=M.CITY+HALL+4&w=25&h=25&ds=mvdemo">
</TD>
<TD>
big cities
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=C.ROSY+BROWN+STROKE&w=34&h=34&ds=mvdemo">
</TD>
<TD>
state boundary
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=L.PH&w=45&h=15&ds=mvdemo">
</TD>
<TD>
interstate highway
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=L.FERRY&w=45&h=15&ds=mvdemo">
</TD>
<TD>
ferry
</TD>
</TR>
<TR> <!-- a separator -->
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/myicons/t.gif" height="15" width="1">
</TD>
<TD>

</TD>
</TR>
<TR>
<TD colspan="2">
<FONT size="+1" color="blue">County Population Density:</FONT>
</TD>
</TR>
<TR>
<TD>
<img src="http://sdolnx2.us.oracle.com:7777/mapviewer/omserver?sty=V.COUNTY_POP_DENSITY&w=180&h=175&ds=mvdemo">
</TD>
<TD>
</TD>
</TR>
</TABLE>

The following is what you will see in a browser:

Tuesday, October 21, 2008

MapViewer for WebLogic kit updated

The latest MapViewer release, version 10.1.3.3 for WebLogic, just saw an update yesterday. Please go to http://www.oracle.com/technology/software/products/mapviewer/index.html for immediate download.

This kit is released under the same bundle name (mapviewer10133wls.zip) but the MapViewer component (mapviewer.ear) in it has been updated to include a few last minute bug fixes. If you have experienced issues with Oracle Maps when doing very fast zooming operations, such as FOI features throwing errors or base map tiles disappearing, then you want to download and deploy this latest kit.

Note again that while this kit is certified on WebLogic Servers, you can also deploy it to all 10.1.3.* versions of OC4J standalone and Oracle App Server. This kit also supersedes all previously released MapViewer 11g previews in functionality and stability.

thanks

LJ

Monday, October 6, 2008

Displaying labels on your FOI objects

You probably noticed that by default, none of the clickable Feature of Interest objects displayed on your map has any label text on it. So how to add labels to your FoIs? There are different approaches based on how your FOI objects are created, and also their geometric types.


Adding an index label to your markers

If your theme-based FOI layer is composed of exclusively Point-type objects (markers), you have an option to automatically attach a sequence number or letter to each marker displayed on the map. This is very useful if all you want to do is to index each marker object and be able to cross-reference them in a list that shows the same objects in a tabular form. The method you will use is MVThemeBasedFOI.enableMarkerSequence( enabled, type). Please check out the online API doc for details on this method. The following screenshot shows how such a map looks like:



Note that in order to make the index number or letter inside each marker visible, you need to make sure the Marker style used by your theme is big enough for the label. Also make sure the color of the text is set to a different color of the marker itself. In the above map, the theme uses a circle Marker style, and it is filled with red color. So you need to make sure it does not use a red color for its text. To change the text color (and font/size if needed), open the Marker style in Map Builder, click the Marker Text button and change the text color. This is shown in the following screen shot:




Individual marker FOI objects

If you manually create and place individual FOI objects on the map, for instance by using the MVFOI.createMarkerFOI() and mapview.addFOI() methods, then it is very easy to add label text to each marker. It is shown in the built-in Oracle Maps tutorial mapviewer/fsmc/tutorial/samples/FOIMarker.html. Look for the statement containing setHTMLElement("#1",20,20), which is the exact method you will use to set a label on top of the marker you are creating.


Theme-based FOI layer

If you are displaying a pre-defined theme based FOI layer, and you want to display a label on each feature of interest, there is a method on MVThemeBasedFOI that does just that: enableLabels(true). Note that this method will work provided the following conditions are met:
1. The theme has label enabled (in other words, if you submit this theme in a plain XML map request the resulted map image should display labels on your theme).
2. The features are of polygon type. If your FOIs are of linestring or point type, the method enableLabels() does nothing for you, unless you enable whole-image rendering for the FOI layer.

Dynamic theme based FOI layer

If you are displaying a JDBC/Dynamic theme based FOI layer, and want to display a label on each feature, simply call the same MVThemeBasedFOI.enableLabels(true) method on your FOI layer. You just need to make sure that your dynamic theme actually contains a label column in its SELECT list, and tell MapViewer the name of that label column as well as the labeling style to be used. As an example, I modified the buffer theme in the built-in tutorial /mapviewer/fsmc/tutorial/samples/jdbcThemeBasedFOI.html to display the label "A1" on each buffer zone:

function updateBuffer()
{
...

baseQuery = "select 'A1' as label, sdo_geom.sdo_buffer(A.location, "+radius+
", 0.005, 'unit=mile arc_tolerance=0.005') location "+
" from "+theme+" A" ;
var theme = '<themes>' +
'<jdbc_query asis="true" spatial_column="location" jdbc_srid="8307" ' +
'render_style="'+bufferStyle+'" datasource="mvdemo" label_column="label" label_style="T.CITY NAME" >' + baseQuery +
'</jdbc_query></theme></themes>' ;
buffertheme = new MVThemeBasedFOI('buffertheme',theme);
buffertheme.setBringToTopOnMouseOver(true);
buffertheme.enableLabels(true);
mapview.addThemeBasedFOI(buffertheme);

...
}