GeoMedia® Smart Client Tech Blog

Technical blog about GeoMedia® Smart Client

Google Street View in Smart Client

14 Comments

In this post I would like to show you how you can open Google Street View on a second screen in Smart Client. Here is the corresponding video, which explains you how to do it.

Once the Google Street View is open, both components are synchronized so that the current position is reflected correctly in each view. In case that you want to reproduce the sample, please make sure that you have an appropriate Google Maps API Key.

What is needed to get it done:

  • Web Site to show Google Street View
  • Smart Client Action defining the url to open the web site
  • Java Extension to handle the communication between the components

 

Step 1: Web Site to show Google Street View

Google Street View may be shown in a simple web site using the standard Google Maps API like this:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Google Street View</title>
<style>
html, body, #pano {height: 100%; margin: 0px; padding: 0px; }
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
<script src="./Scripts/GoogleStreetView.js"></script>
</head>
<body>
<div id="pano"></div>
</body>
</html>

The corresponding JavaScript looks like this:

function getQuerystringParameter(name, _default) {
 var match = RegExp('[?&]' + name + '=([^&]*)')
 .exec(window.location.search);
 return (match && decodeURIComponent(match[1].replace(/\+/g, ' '))) || _default;
}
function initialize() {
 var lat = getQuerystringParameter('lat', null);
 var lng = getQuerystringParameter('lng', null);
 var currentPosition = new google.maps.LatLng(lat, lng);
 var mapOptions = {
  center: currentPosition,
  zoom: 0,
  streetViewControl: true
 };
 var panoramaOptions = {
  position: currentPosition,
  pov: {
   heading: 0,
   pitch: 0
  },
  zoom: 1
 };
 var panorama = new google.maps.StreetViewPanorama(document.getElementById('pano'), panoramaOptions);
 panorama.setVisible(true);
 google.maps.event.addListener(panorama, 'position_changed', function () {
   var mapCenter = panorama.getPosition();
   if (window.GoogleStreetViewScriptingObject) {
     window.GoogleStreetViewScriptingObject.updatePosition(mapCenter.lat(), mapCenter.lng(), panorama.getPov().heading, true);
   }
 });
 google.maps.event.addListener(panorama, 'pov_changed', function () {
   var mapCenter = panorama.getPosition();
   if (window.GoogleStreetViewScriptingObject) {
     window.GoogleStreetViewScriptingObject.updatePosition(mapCenter.lat(), mapCenter.lng(), panorama.getPov().heading, false);
   }
 });
}
google.maps.event.addDomListener(window, 'load', initialize);

Smart Client opens the web page passing the current position as lat and lon parameter in the QueryString by using a standard Smart Client action within the client extension. To ping back from the web page to Smart Client all that needs to be done is to attach to listeners to the google maps events to listen on the pov_changed (Point of Interest) and the position_changed events. In both cases a Smart Client scripting object (see GoogleStreetViewScriptingObject definition below) will be called that centers the Smart Client map to the current position.

Step 2: Smart Client Action Definition

To call this web page, a standard browse action has to be defined in the Smart Client Administrator. As this action will be called from a custom plugin, a naming convention for this action is used. In this case the action has to be called GoogleStreetView to execute correctly. The url needs to point to the web page that has been defined in step 1 and the tab should be hidden so that the user can not click the action.

GoogleStreetViewActionDefinition

Step 3: Client Extension

On the client side, the extension consists of 3 components:

  • GoogleStreetViewLayeredMapComponent
  • GoogleStreetViewPlugin
  • GoogleStreetViewScriptingObject

GoogleStreetViewLayeredMapComponent

The central point of the client extension is the GoogleStreetViewLayeredMapComponent representing a JPanel a GLayerdMapComponent and a Mouse- and GMapListener. As Google Maps sends out a lot of events, it is strongly recommended to draw the current position as an overlay item instead of defining an actual feature. The advantage is that this component may be repainted without reloading the map in the background. The component is implemented using the singleton pattern.

The component itself consists of two svg files one for the current position (simpleMarker.svg) and the other one to display the current viewing angle of the Google Street View POV.  The Icons are loaded on startup like this:

static
{</pre>
try
{
MARKER_IMAGEICON = BatikUtils.loadIcon(GoogleStreetViewLayeredMapComponent.class, "simpleMarker.svg", new Dimension(25, 25));
REDARROW_IMAGEICON = BatikUtils.loadIcon(GoogleStreetViewLayeredMapComponent.class, "Red_Arrow_Up.svg", new Dimension(20, 20));
}
catch (IOException | BatikException | URISyntaxException e)
{
// should never occur
throw new AssertionError();
}
}

From the LayeredMapComponent the paint method has to be implmented like this:


@Override
public void paint(Graphics g)
{
if (streetViewPosition == null)
return;
Graphics2D graphics2d = (Graphics2D) g;
Point2D mapCoordinate = ApplicationContext.getBrowser().getMap().getMapProducer().toMap(streetViewPosition);
graphics2d.drawImage(MARKER_IMAGEICON.getImage(),AffineTransform.getTranslateInstance(mapCoordinate.getX()- MARKER_IMAGEICON.getIconWidth() / 2d,mapCoordinate.getY() - MARKER_IMAGEICON.getIconHeight()/ 2d),null);
AffineTransform redArrowUpTransform = AffineTransform.getTranslateInstance(mapCoordinate.getX()- REDARROW_IMAGEICON.getIconWidth() / 2d,mapCoordinate.getY()- REDARROW_IMAGEICON.getIconHeight() - 10);
redArrowUpTransform.preConcatenate(AffineTransform.getRotateInstance(-streetViewAngle, mapCoordinate.getX(), mapCoordinate.getY()));
graphics2d.drawImage(REDARROW_IMAGEICON.getImage(), redArrowUpTransform, null);
}

In this method we first check if the current street view position is set and if so, the marker representing the current position is shown on the map. When drawing the images, the offsets for the marker and the current angle of the POV are calculated.

From the MouseListener interface we are interested in the mouseClicked event opening the browser window calling the GoogleStreetview action using the current mouse position whenever a double click event occurs. The lat and lng parameters are added to the default url on the fly so that the web page defined in step one is called correctly.


@Override

public void mouseClicked(MouseEvent e)
{
GMap map = ApplicationContext.getBrowser().getMap();
if (map.getState() != MapState.IDLE)
return;
if (e.getClickCount() > 1)
{
setVisible(true);
GCoordinate worldPoint = new GCoordinate(map.getMapProducer().toWorld(e.getPoint()));
GCoordinate latLon = transformCoordinate(worldPoint);
MediaSupportPlugin mediaSupportPlugin = (MediaSupportPlugin) ApplicationContext.getPluginCurator().getByName("MediaSupportPlugin");
RPAction action = ActionDispatcher.getAction("GoogleStreetView");
if (action != null)
{
RPAction instance = action.createChildAction();
instance.putValue("url", String.format("%s?lat=%f&lng=%f", instance.getParam("url"), latLon.getX(), latLon.getY()));
mediaSupportPlugin.browse(instance);
isStreetViewActive = true;
updatePosition(worldPoint, 0);
}
}
}

The GMapListener interface offers us a way to listen to the boundsChanged event so that we can update the GoogleStreetView whenever the bounds change in Smart Client:


@Override

public void boundsChanged(GMap map, GBounds bounds)
{
if (!isStreetViewActive)
return;

GCoordinate worldPoint = ApplicationContext.getBrowser().getMapCenter();
GCoordinate latLon = transformCoordinate(worldPoint);
MediaSupportPlugin mediaSupportPlugin = (MediaSupportPlugin) ApplicationContext.getPluginCurator().getByName("MediaSupportPlugin");
RPAction action = ActionDispatcher.getAction("GoogleStreetView");
RPAction instance = action.createChildAction();
instance.putValue("url", String.format("%s?lat=%f&lng=%f",
instance.getParam("url"), latLon.getX(), latLon.getY()));
mediaSupportPlugin.browse(instance);
updatePosition(worldPoint, 0);
}

Finally the class offers an updatePosition and a transformCoordinate method so that the current position may be updated from outside and the coordinates may be transformed to WGS84 as needed in Google Street View:


protected void updatePosition(GCoordinate currentPosition, float angle)
{
streetViewPosition = currentPosition;
streetViewAngle = angle;
repaint();
}

private GCoordinate transformCoordinate(GCoordinate coordinateToTransform)
{
SpatialReferenceIdentifier sourceSpatialReferenceIdentifier = GeodesyManager.getSpatialReferenceIdentifier(ApplicationContext.getGeodesyManager().getDataCoordinateSystem());
return GeodesyManager.transformCoordinate(coordinateToTransform,sourceSpatialReferenceIdentifier, EPSGID.WGS84);
}

GoogleStreetViewPlugin

The GoogleStreetViewPlugin is a very simple plugin that just initializes the GoogleStreetViewLayeredMapComponent and adds the component and listeners to the map.


@Plugin(alias = "GoogleStreetViewPlugin", vendor = "Intergraph Corp.")
public class GoogleStreetViewPlugin extends AbstractPlugin
{
@Override
public void loadOnStart() throws Exception
{
if (!ActionDispatcher.getInstance().exists("GoogleStreetView"))
return;

GMap map = ApplicationContext.getBrowser().getMap();
GoogleStreetViewLayeredMapComponent layeredMapComponent = GoogleStreetViewLayeredMapComponent.getInstance();
map.getViewerConstraints().getLayeredMapContent().add(layeredMapComponent, new Integer(GLayeredMapContent.DEFAULT_GLAS_LEVEL + 1234));
map.addMouseListener(layeredMapComponent);
map.addGMapListener(layeredMapComponent);
}
}

GoogleStreetViewScriptingObject

To be able to establish a communication between the browser and Smart Client a ScriptingObject is needed.

The main purpose of the GoogleStreetViewScriptingObject is to update the position whenever the status changes in the browser and to hide the LayeredMapComponent in case the browser is closed.


public void updatePosition(double lat, double lng, double heading, boolean updateCoordinates)
{
GCoordinate mapCenter = transformCoordinate(new GCoordinate(lat, lng));
if (updateCoordinates)
ApplicationContext.getBrowser().setMapCenter(mapCenter, ApplicationContext.getBrowser().getScale());
GoogleStreetViewLayeredMapComponent.getInstance().updatePosition(mapCenter, ((float) Math.toRadians(heading)) * -1);
}

@Override
public void setWebBrowser(WebBrowser webBrowser)
{
this.webBrowser = webBrowser;
this.webBrowser.addWebBrowserListener(new IWebBrowserListener()
{
@Override
public void titleChanged(WebBrowser webBrowser, String title) {}

@Override
public void browserClosed(WebBrowser webBrowser)
{
GoogleStreetViewLayeredMapComponent.getInstance().deactivateStreetView();
GoogleStreetViewLayeredMapComponent.getInstance().hideStreetViewFeature();
}

@Override
public void bringToFront(WebBrowser webBrowser){}
});
}

Putting these parts together allows you to display the Google StreetView alongside with you map in Smart Client.

The whole source code may be downloaded from here.

If you need the GMSC user library to reproduce the sample please download it from here.

A quick introduction how to register custom plugins and scripting objects may be found here.

14 thoughts on “Google Street View in Smart Client

  1. Looks great.
    How do I engage the map on-click event? Is this automatically done to the GoogleStreetView Action in Smart Client?
    It looks like you are running a slightly later version -I am running V14.0.0.0_20140218.1 – Where can I get the latest version?

    • What exactly is not working?
      Double click actions are available since version 7 actually.
      What you need is the following:
      1. MouseListener (in my case the GoogleStreetViewLayeredMapComponent)
      2. Implementing the mouseClicked method:
      @Override
      public void mouseClicked(MouseEvent e){…}
      3. Add the listener to the map in a plugin (use the loadOnStart to regiser it
      @Plugin(alias = “GoogleStreetViewPlugin”, vendor = “Intergraph Corp.”)
      public class GoogleStreetViewPlugin extends AbstractPlugin
      {
      @Override
      public void loadOnStart() throws Exception
      {
      if (!ActionDispatcher.getInstance().exists(“GoogleStreetView”))
      return;

      GMap map = ApplicationContext.getBrowser().getMap();
      GoogleStreetViewLayeredMapComponent layeredMapComponent = GoogleStreetViewLayeredMapComponent.getInstance();
      map.addMouseListener(layeredMapComponent);
      }
      }
      Please also have a look at our Java API.

  2. Great job!
    How do i register/load your Client Extension GoogleStreetView (GoogleStreetViewLayeredMapComponent, GoogleStreetViewPlugin, GoogleStreetViewScriptingObject) in a project GMSC???….Smart client action and google street view frame works well but can not seem to load at startup project your client extension …..

    • Hi Mike,
      I have updated the blog post and added a link to download a Smart Client user library containing all resources needed to run GMSC in debug mode on the client side and you can also find a quick introduction how to generate custom plugins and register them in GMSC:
      If you need the GMSC user library to reproduce the sample please download it from here.
      A quick introduction how to register custom plugins and scripting objects may be found here.

      Hope this helps

      • Thank you very much!

      • Hi tanzinge!
        I have a problem with getQuerystringParameter (lat, lng) , when GoogleStreetViewLayeredMapComponent send this url from GMSC to GoogleStreetView.js:
        “http://109.233.127.105/Streetview.html?lat=45, 671 883 & lng = 12.240445”
        The problem are the coordinates with the separator “,” google street view wants “.” is probably due to local settings … I changed String.format (“% s? lat = & lng =% .6 f% .6 f” in the code of GoogleStreetViewLayeredMapComponent but does not work … do you have any idea or suggestion? ?
        Thanks

      • Hi Mike,
        thanks for the hint. You are correct, the sample does not support locale settings.
        It is best to pass the parameters in en-US locale settings. All you need to do is to update the

        mouseClicked and boundsChanged methods like this:

        RPAction instance = action.createChildAction();
        NumberFormat nf = NumberFormat.getInstance(Locale.US);
        String url = String.format("%s?lat=%s&lng=%s", instance.getParam("url"), nf.format(latLon.getX()), nf.format(latLon.getY()));
        instance.putValue("url", url);

        Tested it with English, German, Spanish and Italian locales. If you download the sample again, you will get the corrected version.

  3. Hi Thomas!
    Ok now it works but I have a problem with the accuracy of the coordinates, GMSC returns me this url:
    “http://109.233.127.105/Streetview.html?lat=45.667&lng=12.244”
    For a good positioning and navigation in google street view would need at least 6 digits after the decimal point … do you have any idea or suggestion?
    Thanks

    • Hi,
      NumberFormat gives you the option:
      NumberFormat nf = NumberFormat.getInstance(Locale.US);
      nf.setMinimumFractionDigits(6);

      • Hi Thomas!
        Ok, now it works perfectly! Thank you very much!
        I noticed that when turning on the level Microsoft Bing Maps GMSC freezes and does not work you have to do a forced exit … hopefully in the next gmsc service pack! 😉
        Thank you for your help

  4. Hi,
    With the third step, where do these files need to live on the server once downloaded?

    GoogleStreetViewLayeredMapComponent
    GoogleStreetViewPlugin
    GoogleStreetViewScriptingObject
    and SVG files.

    Thanks,
    Josh

    • Hi,
      you have to export the project to create a jar and sign it with your own key. The signed jar has to be copied to the plugins folder and a jnlp file has to be generated in the same folder like this. You can download a quick how to from here:

  5. Thanks Thomas. Was there meant to be a link at the end of that message?
    I don’t see it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s