Thursday, April 30, 2009

View Microsoft Virtual Earth data in Smallworld

I'm very excited to share with you all that the company I work for (iFactor Consulting) earlier this week released Version 1 of the Virtual Earth Connector for Smallworld. You can read the official announcement here.

The VE Connector makes use of Smallworld Core Spatial Technology Spatial Object Manager (SOM) functionality to render landbase and aerial imagery tiles served up by Virtual Earth on your Smallworld map view.

The product page has a demonstration video that shows the functionality. If you want to try it out for yourself, you can get a full version evaluation release here.

It seems like all the big players (ESRI, MapInfo, Oracle Spatial) are going in this direction and now iFactor presents this capability for Smallworld.

I would like to acknowledge Brad Sileo (iFactor VP Business Development by day and mclib master by night) for getting the original product code going. All of the iFactor executives are also master coders!

We used alot of standards-based functionality to get this to work. Let's see how many buzzwords I can remember :) : Source Forge mclib, C#, SOAP, Web Services, SWAF, SOM, ACP.

Incidentally, from the Magik Components Source Forge library, we made good use of:
  • sw_dotnet_acp (thanks to Pedro Miranda for making this code available in mclib!)
  • mclib_package
  • mclib_http_interface (thanks to Tony Sileo and Brad Sileo for contributing to this!)
So, here's a plug for Magik Components: It can greatly help you with your own projects, too!

We are operating under an agile "release early, release often" model. So, once you have the product loaded, you can check for updates regularly in the "About" dialog and download the latest revision as soon as it becomes available.

Modifying core method behavior without touching core code

The other day I was writing some patches for a custom product (see Developers: patch it yourself). One of the requirements is that the product code needs to be supported at 4, 4.1 and 4.1.1. The change is to add a new dynamic variable to the beginning of map_view.int!do_render().

Originally, I thought I would have to make three different versions of this patch file (one for each CST version that the product supports). That would seem to make sense because at each of the versions, the contents of the method could have changed slightly. As it turns out, the contents had changed between two of the versions.

Having three version-specific patches for a single method ends up being an administrative nightmare because each time a new TSB is released, I need to review all the changes in the TSB for conflicts with my patch code. In some cases this might be necessary because the patch makes significant changes in the middle of the method. But in many cases, the changes we want to put in are at the top or the bottom of the method. In those cases, I discovered that I could use method :define_method_synonym() to wrap my new code around the core code without touching any of the functionality in the core code...


# use define_method_synonym() to wrap some of our behavior
# around the core method without redefining the core behavior.
_if map_view.method(:original!int!do_render|()|) _is _unset
_then
map_view.define_method_synonym(:original!int!do_render|()|,:int!do_render|()|)
_endif
$
_pragma(classify_level=restricted)
_method map_view.int!do_render( _optional post_render_set )
##
## Draws the current visible set in the render thread.
##

_dynamic !ve_connector_current_map_view! << _self

_return _self.original!int!do_render(post_render_set)
_endmethod
$


And if you use the _gather/_scatter keywords, your wrapper does not even need to worry about whether the arguments for a method change from version to version.

This technique is also useful for putting debug code into unshipped source code. Let's say I want to get a traceback every time I get a user_error dialog with the word "coordinate" in the dialog message. The dialog is actually activated somewhere in basic_window.show_alert(). This method code, however, is unshipped so your test code might look like...



_if basic_window.method(:original!show_alert|()|) _is _unset
_then
basic_window.define_method_synonym(:original!show_alert|()|,:show_alert|()|)
_endif
$
_pragma(classify_level=restricted, usage={redefinable})
_method basic_window.show_alert(message, _optional yes_message, no_message, default, mode)

_if message.canonical.index_of_seq("coordinate") _isnt _unset
_then
!traceback!()
_endif

_return _self.original!show_alert(message,yes_message,no_message,default,mode)
_endmethod
$


NOTE: the method comments on :define_method_synonym() say that it is "severely deprecated." But until the time that this method is removed from the core code, I find it a very useful tool.

NOTE NOTE: While this technique does minimize the touch points of your code with the core code, you still need to be aware of the classifications (basic,advanced,restricted) of a method to understand how/if you should modify the method.

Wednesday, April 22, 2009

Determining map_view Pixel DPI

The other day I was trying to figure out the pixel DPI on my map_view so that I could size a raster_image on the map. I had always thought that !window_system!.screen_resolution() would do that for me. It seemed to work as long as I put in some additional empirical fudge factor. I discovered later that if I am working with map_view, I can use map_view.pixel_size to calculate DPI. map_view.pixel_size returns the size of a pixel in millimetres so to convert that to DPI...

_method map_view.if!dpi
## if!dpi : integer
##
## returns _self's DPI

_return (1/_self.pixel_size) * 25.4
_endmethod

Thursday, April 16, 2009

Exposing hidden data from Smallworld to FME

A member of the sw-gis group recently posted a question asking how to export join attribute information from Smallworld via FME. I responded to the group but thought the answer might be worth sharing here as well.

Question:

I'm using FME with Smallworld 4.0. How can I export join attributes (e.g., export transformer bank id with the transformer)? I coudn't find it in the FME workbench.


Answer:

I think the easiest way to do this is to use the fme_pseudo_field_factory.

For example...

If you’re your record exemplar is :transformer and the transformer_bank ID attribute is called :transformer_bank_id, then you can define a new FME Pseudo Field...

_pragma(classify_level=basic, usage={redefinable})
## used by FME SWAF interface to allow a customizer to add
## pseudo fields to the schema for Smallworld tables to be
## passed to FME.
transformer.define_shared_constant(:fme_pseudo_fields,
{fme_pseudo_field_factory.new_derived_field(:transformer_bank_id,:extdb_string)},
_false)
$

You can actually define more than one fme_pseudo_field for a given record exemplar if you want to.

Declaring :transformer_bank_id as a new derived field will make this "field" available to the FME feature. When FME asks a "transformer" for its :transformer_bank_id, it will simply call transformer_bank_id and use that value in the workspace.

There is nothing special about the naming convention of the pseudo field. The only thing to keep in mind is that the class on which you define :fme_pseudo_fields must respond (either as a method or field call) to the name of the derived field.

So you could actually accomplish the same thing as above with...

_pragma(classify_level=basic, usage={redefinable})
## used by FME SWAF interface to allow a customizer to add
## pseudo fields to the schema for Smallworld tables to be
## passed to FME.
transformer.define_shared_constant(:fme_pseudo_fields,
{fme_pseudo_field_factory.new_derived_field(:bank_id,:extdb_string)},
_false)
$


As long as you also have defined a method...

_method transformer.bank_id
## bank_id : integer
##
## returns self’s transformer_bank_id

_return _self.transformer_bank_id
_endmethod
$

The FME fme_pseudo_fields concept is very powerful because it allows you to send data to FME that is not stored in a single attribute in Smallworld.

I have used it in the past for exporting data from Physical Network Inventory (PNI). If you are familiar with that datamodel you will know that there are many layers of relationships. Some of the records have geometries and some do not. A customer wanted to export all underground and aerial routes (these are the features with geometry) but wanted to label the features in the exported format with data from a related non-RWO record. Using the fme_pseudo_fields approach made it very easy to create a Magik method that created a suitable labelling string based on the related records but expose the results to FME as a feature attribute.