All posts by tolon

Full Width and Height Google Maps with Angular Material and Bower

After having some trouble getting a full width and height map working with Angular Material, I am sharing my solution to hopefully spare others the same pain.

Screen Shot 2016-01-31 at 21.02.38

The solution isn’t anything special, but I did find it tricky as I hadn’t used Angular Material before.

For this example, create a new angularjs scaffold as described briefly at https://github.com/yeoman/generator-angular, or in a more detailed tutorial at http://yeoman.io/codelab/index.html.

Once your initial scaffold is complete, add the google-maps and angular-material bower packages using:

bower install --save google-maps angular-material

Replace the scaffolded content with a modified ng-view div, like this:

...
    <!-- Add your site or application content here -->
    <div ng-view="" flex class="layout-fill"></div>

    <!-- Google Analytics: change UA-RAAAWR-! to be your site's ID -->
...

Replace the main.html view content with this:

<md-content layout="column" flex class="layout-fill" class="content-wrapper">
  <div flex class="layout-fill" id="map"></div>
  </div>
</md-content>

Add the loader script into your controller main.js:

angular.module('srcApp')
  .controller('MainCtrl', function () {

    GoogleMapsLoader.load(function (google) {
      var mapOptions = {
        zoom: 11,
        center: new google.maps.LatLng(54.5767, -1.2355)
      };

      _map = new google.maps.Map(document.getElementById('map'), mapOptions);
    });

  });

And that’s it! I’ve used this technique on my NE Travel Data website showing live CCTV camera images on top of a Google Maps traffic layer. If you have the time, check it out at the link below.

Tolon Traffic Cameras

The code for this example can be found on Github at https://github.com/TolonUK/angularjs-googlemaps.

Beware setlocale() in Visual C++ 2012 onwards!

My new article in ACCU’s CVu magazine describes how investigating a crash in a popular open source library led to the discovery of a change in behaviour in the C runtime library implementation that could have a significant detrimental impact on applications using C runtime locales across multiple threads that are compiled with Visual C++ 2012 & 2013 – http://accu.org/var/uploads/journals/CVu264.pdf#page=12

New HRESULT Error Code List

Frustrated by the lack of a simple resource on the Internet that associates HRESULT values with symbols and descriptions, I’ve created my own. This new list of HRESULT codes should hopefully help anyone programming on Windows systems (or even just having to interface with them).

For those who don’t know, HRESULTs are basically error codes that are used by some software systems. These are the kind of codes that you’ll occasionally see in cryptic error messages, such as ‘Operation failed, error code 0x80001234’.

There are a couple of minor issues to sort out, but the first generated list can be access from http://codeswamp.tolon.co.uk.

Tolon NoteKeeper 0.10 Released

notekeeper-splash-0.10

Tolon NoteKeeper 0.10 (build 20130104) is now available.

This is the first proper release in a long time and most of the changes are to cope with a new build environment and library updates. There are no real big feature changes, apart from the spell-checking engine and some code improvements.

Download

As with all software, please remember to save regularly and keep regular backups.

The latest version can be downloaded from:

http://www.tolon.co.uk/software/notekeeper/download

Changes

New Features

  • New spell checking engine.
  • New toolbar icons.
  • New NoteKeeper icon.
  • Minimize-to-tray option.

Miscellaneous Changes

  • Reduced build warnings.
  • Added CRC checks for node data.
  • Now using MSVC9 compiler.
  • Now using paintlib 2.6.2

As always, please let me know if you have any problems.

Best Regards,
Tolon

HRESULT Lookup Utility

When dealing with software on Windows we are often confronted with HRESULT error codes, presented either as hexadecimal (e.g. 0x80070057) or as a negative decimal number (-2147024809). To understand what the error code means we look it up, usually in the ‘Error Lookup’ utility that comes with Visual Studio, but this has significant limitations.

Presenting the ‘Error Lookup’ with a a hex HRESULT code will show us the error message, but it does not show us the symbol as it is defined in the Platform SDK (usually winerror.h). So for 0x80070057, it will tell us that the message is ‘The parameter is incorrect’, but it will not tell us the original symbol (E_INVALIDARG).

Another limitation is that we cannot look up a HRESULT symbol to obtain the error code value (and message).

This is where my new utility will help.

hrlookup has been written to provide more information about HRESULT error codes and symbols so that we will no longer need to search online for well-known values just to find the symbol name.

Only a command-line version is available just now, but I’m planning to create a user interface soon.

Download hrlookup from here

Transporting SDO_GEOMETRY objects across an Oracle DBLINK in PLSQL

At my work we have a system that generally speaking consists of two Oracle 10g databases, the ‘SpatialDB’ and the ‘AppDB’. The SpatialDB holds a multi-gigabyte set of static national road-network geometry data and the AppDB holds the dynamic runtime data. A database link is defined in the AppDB so that it can query the SpatialDB and invoke stored procedures.

Generally what happens is that an action somewhere in the system (user interaction, external system communications, etc.), will result in something happening in the database.

In this case a package method in the AppDB needed to obtain a geometry from the SpatialDB. Surprisingly, this turned out to be a quite a difficult thing to achieve. It seems that along with cursors, LOBs and objects in general, an SDO_GEOMETRY cannot be transported across a database link as an out parameter of a package method. The following is a summary of the methods I discovered for overcoming this.

Method #1 – Use the SQL Engine

Whilst PLSQL cannot cope with transporting objects across a database link, it turns out that the SQL engine copes just fine. Objects from the remote database can be selected into a local table and then that table can be queried to obtain the result.

INSERT INTO LOCAL_TABLE SELECT ORA_GEOMETRY FROM REMOTE_TABLE@SPATIALDB;
SELECT ORA_GEOMETRY FROM LOCAL_TABLE;

Method #2 – Transport via WKT

Oracle 10g contains methods for transforming a geometry into a format called ‘Well Known Text’, which is a string representation for geometries. Two methods are provided for these transformations, SDO_UTIL.TO_WKTGEOMETRY and SDO_UTIL.FROM_WKTGEOMETRY.

The technique here is to transform the geometry into a WKT string on the remote side, transfer the string across as a PLSQL out parameter, then transform the string back into a geometry object on the local side.

However, the following limitations must be noted.

  • Transfering the string as a VARCHAR2 type will limit its length to 32767 characters.
  • No 3D support.
  • The SRID (reference system) is not preserved.
  • Complex geometries are not supported.

Method #3 – Transport via GML

Having read that Oracle’s GML implementation gave better geometry support, I thought I would give it a try instead of WKT. I was a bit concerned about the amount of valuable VARCHAR2 space that would be wasted on XML tags, but I never got as far as testing whether it would be an issue.

So in a similar fashion to the WKT support, in the SDO_UTIL package you will find a TO_GMLGEOMETRY method to convert your geometry object into a GML string. Unfortunately for users of 10g, you will not find any corresponding method to convert GML into a geometry object. Disappointing to say the least.

Method #4 – Transport via Custom String

My final solution was to write my own conversion routine that simply took the geometry object and encoded its data members into a CSV string. This format does not attempt to interpret the geometry data, so all geometries and all dimensions are supported. SRID data is also preserved and because the transformation is doing less work that the WKT methods, it turns out to be faster too.

The tables below give the times recorded for encoding and decoding geometries of varying sizes and quantities. Times are in seconds.

2 Vertex Line Encoding
Time taken to encode a line with 2 vertices.

WKT GML Custom
1 Geometry 0.009328 0.002073 0.000057
100 Geometries 0.124836 0.154757 0.002367

2 Vertex Line Decoding
Time taken to decode a line with 2 vertices.

WKT GML Custom
1 Geometry 0.001482 0.009657 0.000616
100 Geometries 0.036376 0.081836 0.046199

100 Vertex Line Encoding
Time taken to encode a line with 100 vertices.

WKT GML Custom
1 Geometry 0.001141 0.003194 0.000627
100 Geometries 0.293802 0.273061 0.06347

100 Vertex Line Decoding
Time taken to decode a line with 2 vertices.

WKT GML Custom
1 Geometry 0.001889 0.003083 0.002381
100 Geometries 0.24252 0.251887 0.239928

Observations

The encoding of the sdo_geometry objects to a custom string format is clearly faster that encoding to WKT or GML using the stand methods in SDO_UTIL. This is especially true for simple geometries with few vertices.

The decoding of the custom string format back into sdo_geometry objects did not exhibit the same performance benefit, although the performance of decoding one simple 2-vertex geometry did show a marked improvement.

Conclusions

There are two main reasons I can see for the custom string encoding methods having a performance advantage.

  1. The custom string format is a closer representation of the binary format of the object with less translation required by the encoding logic.

  2. The custom string format is implemented in PL/SQL and not Java like the other methods. Although Java code should have a performance advantage, it seems likely that the cost of switching context between PL/SQL and Java is a significant overhead.

Code

I hope you find the code useful. If you find any bugs or make any improvements, I would appreciate the feedback.

code on github

Spec

CREATE OR REPLACE PACKAGE pg_user_geom_util AS
-- copyright (c) 2012, Nicander Ltd
	
	-- Translates a geometry object into a string representation 
	-- for storage and transport. See also StringToGeom.
	FUNCTION GeomToString(pGeom IN mdsys.sdo_geometry ) RETURN VARCHAR2 DETERMINISTIC;
	PROCEDURE Test_GeomToString;
	
	-- Translate a geometry string representation into a geometry object.
	-- See also GeomToString.
	FUNCTION StringToGeom(pString IN VARCHAR2) RETURN MDSYS.SDO_GEOMETRY DETERMINISTIC;
	PROCEDURE Test_StringToGeom;
	
	-- Tests the conversion between sdo_geometry objects and a string representation.
	-- Tests for pGeomCount geometries, each with pGeomVertexCount vertices.
	PROCEDURE PerfTest_GeomStrings(pGeomCount IN INTEGER, pGeomVertexCount IN INTEGER);
	
	-- Runs PerfTest_GeomStrings(INTEGER, INTEGER) for a selection of values.
	PROCEDURE PerfTest_GeomStrings;
	
END pg_user_geom_util;
/

show errors package pg_user_geom_util;

Body

CREATE OR REPLACE PACKAGE BODY pg_user_geom_util AS
-- copyright (c) 2012, Nicander Ltd

-- Gets the next token in a separated list. See StringToGeom for an example.
PROCEDURE get_token( iStart   IN NUMBER,
           sPattern in VARCHAR2,
           sBuffer  in VARCHAR2,
           sResult  OUT NOCOPY VARCHAR2,
           iNextPos OUT NOCOPY NUMBER ) IS
  nPos1 number;
  nPos2 number;
BEGIN
  nPos1 := Instr(sBuffer, sPattern, iStart);
  IF nPos1 = 0 then
  sResult := rtrim(ltrim(substr(sBuffer, iStart, LENGTH(sBuffer) - iStart)));
  ELSE
  sResult  := Rtrim(Ltrim(Substr(sBuffer, iStart, nPos1 - iStart)));
  iNextPos := nPos1 + 1;
  END IF;
END;

--------------------------------------------------------------------------------
  
FUNCTION NumberToString(pNumber IN NUMBER) RETURN VARCHAR2 IS
BEGIN
  IF (pNumber IS NULL) THEN
    RETURN 'NULL';
  ELSE
    RETURN TO_CHAR(pNumber);
  END IF;
EXCEPTION
  WHEN OTHERS THEN
    --DBMS_OUTPUT.PUT_LINE('Exception caught: ' || DBMS_UTILITY.FORMAT_ERROR_STACK() || ', ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE());
    RETURN 'NULL';
END;
  
--------------------------------------------------------------------------------
  
FUNCTION StringToNumber(pString IN VARCHAR2) RETURN NUMBER IS
BEGIN
    RETURN TO_NUMBER(pString);
EXCEPTION
  WHEN OTHERS THEN
    --DBMS_OUTPUT.PUT_LINE('Exception caught: ' || DBMS_UTILITY.FORMAT_ERROR_STACK() || ', ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE());
    RETURN NULL;
END;
  
--------------------------------------------------------------------------------
  
  -- String format is as follows:
  --
  -- <GTYPE>,<SRID>,<POINT>,<ELEM_INFO>,<ORDINATES>
  --
  -- Where:
  -- <GTYPE> := <OPTIONAL_NUMBER>
  -- <SRID> := <OPTIONAL_NUMBER>
  -- <POINT> := NULL | SDO_POINT_TYPE, OPTIONAL_NUMBER, OPTIONAL_NUMBER, OPTIONAL_NUMBER
  -- <ELEM_INFO> := NULL | SDO_ELEM_INFO_ARRAY, NUMBER, {OPTIONAL_NUMBER, ...}
  -- <ORDINATES> := NULL | SDO_ORDINATE_ARRAY, NUMBER, {OPTIONAL_NUMBER, ...}
  --
  -- <OPTIONAL_NUMBER> := NULL | NUMBER
  -- entries in curly braces are repeated from n times
FUNCTION GeomToString(pGeom IN mdsys.sdo_geometry ) RETURN VARCHAR2 DETERMINISTIC IS
  vString VARCHAR(32767);
  vCount NUMBER;
BEGIN
  -- GTYPE
  vString := NumberToString(pGeom.SDO_GTYPE);
  
  -- SRID
  vString := vString || ',' || NumberToString(pGeom.SDO_SRID);
  
  -- POINT
  IF (pGeom.SDO_POINT IS NULL) THEN
    vString := vString || ',NULL';
  ELSE
    vString := vString || ',SDO_POINT_TYPE,' || NumberToString(pGeom.SDO_POINT.X) || ',' || NumberToString(pGeom.SDO_POINT.Y) || ',' || NumberToString(pGeom.SDO_POINT.Z);
  END IF;
  
  -- ELEM_INFO
  IF (pGeom.SDO_ELEM_INFO IS NULL) THEN
    vString := vString || ',NULL';
  ELSE
    vCount := pGeom.SDO_ELEM_INFO.COUNT;
    vString := vString || ',SDO_ELEM_INFO_ARRAY,' || vCount;
    FOR i IN 1..vCount LOOP
      vString := vString || ',' || NumberToString(pGeom.SDO_ELEM_INFO(i));
    END LOOP;
  END IF;
  
  -- ORDINATES
  IF (pGeom.SDO_ORDINATES IS NULL) THEN
    vString := vString || ',NULL';
  ELSE
    vCount := pGeom.SDO_ORDINATES.COUNT;
    vString := vString || ',SDO_ORDINATE_ARRAY,' || vCount;
    FOR i IN 1..vCount LOOP
      vString := vString || ',' || NumberToString(pGeom.SDO_ORDINATES(i));
    END LOOP;
  END IF;
  
  RETURN vString;
END;
  
--------------------------------------------------------------------------------
  
PROCEDURE Test_GeomToString IS
  vGeom MDSYS.SDO_GEOMETRY;
  vString VARCHAR2(32767);
BEGIN
  -- TEST CASE 1 - POINT
  dbms_output.put_line('----- TEST CASE 1 -----');
  vGeom := mdsys.sdo_geometry( 2001, NULL, MDSYS.SDO_POINT_TYPE(12345, 67890, NULL), null, null );
  dbms_output.put_line('Src Geom: ' || SDO_UTIL.TO_WKTGEOMETRY(vGeom));
  vString := GeomToString(vGeom);
  IF (vString IS NULL) THEN
    vString := '<NULL>';
  END IF;
  dbms_output.put_line('Dest String: ' || vString);
  
  -- TEST CASE 2 - LINE
  dbms_output.put_line('----- TEST CASE 2 -----');
  vGeom := MDSYS.SDO_GEOMETRY(2002,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(652737.95,6589964.213,652741.222,6589983.148,652746.77,6590018.745,652752.029,6590050.2,652759.351,6590085.732));
  dbms_output.put_line('Src Geom: ' || SDO_UTIL.TO_WKTGEOMETRY(vGeom));
  vString := GeomToString(vGeom);
  IF (vString IS NULL) THEN
    vString := '<NULL>';
  END IF;
  dbms_output.put_line('Dest String: ' || vString);
END;
  
--------------------------------------------------------------------------------
  
FUNCTION StringToGeom(pString IN VARCHAR2) RETURN MDSYS.SDO_GEOMETRY DETERMINISTIC IS
  vGeom MDSYS.SDO_GEOMETRY;
  vPos NUMBER := 1;
  vNextPos NUMBER := 1;
  vToken VARCHAR2(32767);
  vSep CONSTANT VARCHAR2(2) := ',';
  vCount NUMBER;
BEGIN
  vGeom := MDSYS.SDO_GEOMETRY('POINT(0 0)');
  
  -- GTYPE
  get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
  vGeom.SDO_GTYPE := StringToNumber(vToken);
  
  -- SRID
  get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
  vGeom.SDO_SRID := StringToNumber(vToken);
  
  -- POINT
  get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
  IF (UPPER(vToken) LIKE 'SDO_POINT_TYPE') THEN
    vGeom.SDO_POINT := MDSYS.SDO_POINT_TYPE(NULL, NULL, NULL);
    get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
    vGeom.SDO_POINT.X := StringToNumber(vToken);
    get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
    vGeom.SDO_POINT.Y := StringToNumber(vToken);
    get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
    vGeom.SDO_POINT.Z := StringToNumber(vToken);
  ELSE
    vGeom.SDO_POINT := NULL;
  END IF;
  
  -- ELEM_INFO
  get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
  IF (UPPER(vToken) LIKE 'SDO_ELEM_INFO_ARRAY') THEN
    vGeom.SDO_ELEM_INFO := MDSYS.SDO_ELEM_INFO_ARRAY();
    get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
    vCount := StringToNumber(vToken);
    IF (vCount > 0) THEN
      vGeom.SDO_ELEM_INFO.EXTEND(vCount);
      FOR i IN 1..vCount LOOP
        get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
        vGeom.SDO_ELEM_INFO(i) := StringToNumber(vToken);
      END LOOP;
    END IF;
  ELSE
    vGeom.SDO_ELEM_INFO := NULL;
  END IF;
  
  -- ORDINATES
  get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
  IF (UPPER(vToken) LIKE 'SDO_ORDINATE_ARRAY') THEN
    vGeom.SDO_ORDINATES := MDSYS.SDO_ORDINATE_ARRAY();
    get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
    vCount := StringToNumber(vToken);
    IF (vCount > 0) THEN
      vGeom.SDO_ORDINATES.EXTEND(vCount);
      FOR i IN 1..vCount LOOP
        get_token(vPos, vSep, pString, vToken, vNextPos); vPos := vNextPos;
        vGeom.SDO_ORDINATES(i) := StringToNumber(vToken);
      END LOOP;
    END IF;
  ELSE
    vGeom.SDO_ORDINATES := NULL;
  END IF;

  RETURN vGeom;
END;
  
--------------------------------------------------------------------------------
  
PROCEDURE Test_StringToGeom IS
  vString VARCHAR2(32767);
  vGeom MDSYS.SDO_GEOMETRY;
BEGIN
  -- TEST CASE 1 - POINT
  dbms_output.put_line('----- TEST CASE 1 -----');
  vString := '2001,NULL,SDO_POINT_TYPE,12345,67890,NULL,NULL,NULL';
  dbms_output.put_line('src string: ' || vString);
  vGeom := StringToGeom(vString);
  dbms_output.put_line('dest geom: ' || SDO_UTIL.TO_WKTGEOMETRY(vGeom));

  -- TEST CASE 2 - LINE
  dbms_output.put_line('----- TEST CASE 2 -----');
  vString := '2002,NULL,NULL,SDO_ELEM_INFO_ARRAY,3,1,2,1,SDO_ORDINATE_ARRAY,10,652737.95,6589964.213,652741.222,6589983.148,652746.77,6590018.745,652752.029,6590050.2,652759.351,6590085.732';
  dbms_output.put_line('src string: ' || vString);
  vGeom := StringToGeom(vString);
  dbms_output.put_line('dest geom: ' || SDO_UTIL.TO_WKTGEOMETRY(vGeom));
END;

--------------------------------------------------------------------------------

PROCEDURE PerfTest_GeomStrings(pGeomCount IN INTEGER, pGeomVertexCount IN INTEGER) IS
  TYPE geom_array IS VARRAY(1000) OF MDSYS.SDO_GEOMETRY;
  vGeoms geom_array := geom_array();
  vWKTGeoms geom_array := geom_array();
  vGMLGeoms geom_array := geom_array();
  vCustomGeoms geom_array := geom_array();
  TYPE string_array IS VARRAY(1000) OF VARCHAR2(32767);
  vWkts string_array := string_array();
  vGmls string_array := string_array();
  vCustoms string_array := string_array();
  vWktStart timestamp;
  vWktEncDelta interval day to second;
  vWktDecDelta interval day to second;
  vGmlStart timestamp;
  vGmlEncDelta interval day to second;
  vGmlDecDelta interval day to second;
  vCustomStart timestamp;
  vCustomEncDelta interval day to second;
  vCustomDecDelta interval day to second;
  vTempOrdinates MDSYS.SDO_ORDINATE_ARRAY;
BEGIN
  dbms_output.put_line('----- ' || pGeomCount || ' geometries with ' || pGeomVertexCount || ' vertices -----');

  vGeoms.extend(pGeomCount);
  vWKTGeoms.extend(pGeomCount);
  vGMLGeoms.extend(pGeomCount);
  vCustomGeoms.extend(pGeomCount);
  vWkts.extend(pGeomCount);
  vGmls.extend(pGeomCount);
  vCustoms.extend(pGeomCount);
  
  FOR i IN 1..pGeomCount LOOP
    vTempOrdinates := MDSYS.SDO_ORDINATE_ARRAY();
    vTempOrdinates.extend(pGeomVertexCount*2);
    FOR j IN 1..(pGeomVertexCount*2) LOOP
      vTempOrdinates(j) := i + j;
    END LOOP;
    vGeoms(i) := mdsys.sdo_geometry(2002, NULL, NULL, MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1), vTempOrdinates);
  END LOOP;
  
  -- encode to WKT
  vWktStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vWkts(i) := SDO_UTIL.TO_WKTGEOMETRY(vGeoms(i));
  END LOOP;
  vWktEncDelta := systimestamp - vWktStart;
  
  -- encode to GML
  vGmlStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vGmls(i) := SDO_UTIL.TO_GMLGEOMETRY(vGeoms(i));
  END LOOP;
  vGmlEncDelta := systimestamp - vGmlStart;
  
  -- encode to custom
  vCustomStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vCustoms(i) := GeomToString(vGeoms(i));
  END LOOP;
  vCustomEncDelta := systimestamp - vCustomStart;
  
  -- decode from WKT
  vWktStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vWKTGeoms(i) := SDO_UTIL.FROM_WKTGEOMETRY(vWkts(i));
  END LOOP;
  vWktDecDelta := systimestamp - vWktStart;
  
  -- decode from GML
  vGmlStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vGMLGeoms(i) := SDO_UTIL.FROM_GMLGEOMETRY(vGMLs(i));
  END LOOP;
  vGmlDecDelta := systimestamp - vGmlStart;
  
  -- decode from custom
  vCustomStart := systimestamp;
  FOR i IN 1..pGeomCount LOOP
    vCustomGeoms(i) := StringToGeom(vCustoms(i));
  END LOOP;
  vCustomDecDelta := systimestamp - vCustomStart;
  
  dbms_output.put_line('Encoding to WKT took ' || extract(second from vWktEncDelta) || ' seconds');
  dbms_output.put_line('Encoding to GML took ' || extract(second from vGmlEncDelta) || ' seconds');
  dbms_output.put_line('Encoding to custom took ' || extract(second from vCustomEncDelta) || ' seconds');
  
  dbms_output.put_line('Decoding from WKT took ' || extract(second from vWktDecDelta) || ' seconds');
  dbms_output.put_line('Decoding from GML took ' || extract(second from vGmlDecDelta) || ' seconds');
  dbms_output.put_line('Decoding from custom took ' || extract(second from vCustomDecDelta) || ' seconds');
  
  dbms_output.put_line('----------------------------------------');
  dbms_output.put_line(' ');
END;

--------------------------------------------------------------------------------

PROCEDURE PerfTest_GeomStrings IS
BEGIN
  -- geometries with 2 vertices
  PerfTest_GeomStrings(1, 2);
  PerfTest_GeomStrings(10, 2);
  PerfTest_GeomStrings(100, 2);
  PerfTest_GeomStrings(1000, 2);
  
  -- geometries with 10 vertices
  PerfTest_GeomStrings(1, 10);
  PerfTest_GeomStrings(10, 10);
  PerfTest_GeomStrings(100, 10);
  PerfTest_GeomStrings(1000, 10);

  -- geometries with 100 vertices
  PerfTest_GeomStrings(1, 100);
  PerfTest_GeomStrings(10, 100);
  PerfTest_GeomStrings(100, 100);
  PerfTest_GeomStrings(1000, 100);

  -- geometries with 1000 vertices
  PerfTest_GeomStrings(1, 1000);
  PerfTest_GeomStrings(10, 1000);
  PerfTest_GeomStrings(100, 1000);
  PerfTest_GeomStrings(1000, 1000);

END;

END pg_user_geom_util;
/

show errors package body pg_user_geom_util;

Lightweight Cooperative Multitasking with Boost.Context

I’m writing this in response to Kenny Kerr’s article Lightweight Cooperative Multitasking with C++ in the August 2012 issue of MSDN Magazine, in which Kenny describes a method of implementing cooperative multitasking in C++.

What is Cooperative Multitasking?

This is a method of achieving concurrency in an application without using multiple threads. The basic idea is that you can have multiple tasks running on just a single thread. Now of course only one of these tasks can be running at any one time, so each task must perform some work and then explicitly yield to another task.

Ideally whatever method is used for beginning tasks and yielding should automatically provide a separate context for each task. That is, each task should have its own stack and register data.

Why Kenny’s Article is Cool

Kenny’s article is all about how you can implement your own cooperative multitasking in C++ without having to resort to a specific API calls or a separate library. Yes it’s based on a previously known technique by Tom Duff, but it demonstrates that an awful lot can be done with a relatively small amount of code. This is why I like C++; it gives you the power to do things like this.

As cool as it is, I don’t think I’ll be adding it to my production code any time soon. Besides the problems I’d have getting it past code review, I found a better solution…

Enter Boost.Context

At the end of the article, Kenny reveals his hopes (and dreams?) of a day when he can implement cooperative multitasking in C++ without having to resort to unmaintainable macro hacks.

As luck would have it, on the 20th August 2012 version 1.51 of the Boost C++ libraries was released which contained the new library Boost.Context by Oliver Kowalke. This is a fairly low-level library, but it does provide a basic cooperative multitasking library with a separate context for each task.

Average Task, Boost.Context Style

In his article, Kenny implements a basic application to take numeric input from the command line and produce some statistics. It’s not what I’d call a classic concurrency problem, but it serves the purpose of demonstrating the tasks yielding control only when appropriate. I’ve taken the code and re-implemented it using Boost.Context. Enjoy!

#include <boost\context\all.hpp>

namespace ctx = boost::ctx;

ctx::fcontext_t fc_avg, fc_input, fc_main;
bool b_quit = false;

// --- The Average Task ---
// Computes the average of the input data and 
// then yields execution back to the main task
//-------------------------
// struct average_args - data for the task
// average_yield() - wrapper for yielding execution back to the main task
// average_task() - the task logic
// ------------------------
struct average_args
{
	int * source;
	int sum;
	int count;
	int average;
	int task_;
};
void average_yield() { ctx::jump_fcontext(&fc_avg, &fc_main, 0); }
void average_task(intptr_t p)
{
	average_args* args = (average_args*)p;
	args->sum = 0;
	args->count = 0;
	args->average = 0;
	while (true)
	{
		args->sum += *args->source;
		++args->count;
		args->average = args->sum / args->count;
		average_yield();
	}

	printf("ERROR: should not reach the end of average function\n");
}

// --- The Input Task ---
// Reads a number as input from the console and 
// then yields execution back to the main task
// ----------------------
// struct input_args - data for the task
// input_yield() - wrapper for yielding execution back to the main task
// input_task() - the task logic
// ----------------------
struct input_args
{
	average_args* aa;
	int * target;
	int task_;
};
void input_yield() { ctx::jump_fcontext(&fc_input, &fc_main, 0); }
void input_task(intptr_t p)
{
	input_args* pia = (input_args*)p;

	while (true)
	{
		printf("number: ");
		if (!scanf_s("%d", pia->target))
		{
			b_quit = true;
			return;
		}
    
		input_yield();
	}

	printf("ERROR: should not reach the end of input function\n");
}

void main()
{
	int share = -1;
	average_args aa = {&share};
	input_args ia = {&aa, &share};
	ctx::stack_allocator alloc;

	// construct the input task
	fc_input.fc_stack.base = alloc.allocate(ctx::minimum_stacksize());
	ctx::make_fcontext(&fc_input, input_task);

	// construct the average task
	fc_avg.fc_stack.base = alloc.allocate(ctx::minimum_stacksize());
	ctx::make_fcontext(&fc_avg, average_task);

	while (!b_quit)
	{
		ctx::jump_fcontext( &fc_main, &fc_input, (intptr_t) &ia);
		ctx::jump_fcontext( &fc_main, &fc_avg, (intptr_t) &aa);
		printf("sum=%d count=%d average=%d\n", aa.sum, aa.count, aa.average);
	}
	
	printf("main: done\n");
}

Footnotes

The Boost.Context library has been described as a good candidate for providing low-level cooperative multitasking support to the developing Boost.Coroutine library. Coroutine will provide a more high-level, abstract way of applying these concurrency techniques in your code.

I’m looking forward to Kenny’s next article in which he has promised to show how the Visual C++ team is working on what must be some kind of cooperative multitasking that he’s called ‘resumable functions’.

Reading an In-Memory SQLite Database

I’m currently working on a utility to improve upon the basic Error Lookup utility found in Microsoft Visual Studio. I am trying to improve it by collating all the error codes and messages found in the Microsoft Platform SDK into a database, which can be accessed and queried in a similar way to the existing utility, but with a extra bells and whistles (think free-text search, partial code matching, etc.). To make distribution of the utility easier, I decided to try embedding my error database into the executable file, which was straightforward. Using the database however required a bit more work, which I am presenting here in the hope that others will find it useful.

Embedding a SQLite Database in a Windows EXE or DLL

The data I am working with is in a SQLite database, which is a database consisting of a single file. To embed this file in my Visual Studio project I did the following.

  1. Add a resource file to your project (only if you don’t have one already).
  2. Right-click on the resource file and choose ‘Add Resource’.
  3. Select ‘Import…’ and choose your database file.
  4. Enter a resource type of ’10’.

Using an Embedded SQLite Database

Now you have your data embedded in your application, first you need to obtain a pointer to the data bytes and next you need to get SQLite to read the in-memory data. Although this looks simple, it took a few nights to understand how the SQLite storage model worked in order to get it working in a truly read-only manner.

This is how I do it in my code:

void open_mem_db()
{
    char errcode = 0;

    void* pBuffer = NULL;
    HRSRC hr = ::FindResource(NULL, MAKEINTRESOURCE(IDR_ERRORCODEDB), RT_RCDATA);

    if (hr)
    {
        HGLOBAL hg = ::LoadResource(NULL, hr);

        if (hg)
        {
            DWORD dwSize = ::SizeofResource(NULL, hr);
            pBuffer = ::LockResource(hg);

            set_mem_db(pBuffer, dwSize);

            int nInitResult = readonlymemvfs_init();
            assert(nInitResult == SQLITE_OK);
            errcode = sqlite3_open_v2( "_", &s_database,
                SQLITE_OPEN_READONLY, READONLY_MEM_VFS_NAME );
        }
    }
}

Remember to change IDR_ERRORCODEDB to your resource identifier. This makes use of some extra code to allow SQLite to read data straight from memory, which can be downloaded from http://www.tolon.co.uk/download/readonlymemvfs-0.1.zip or from github. Use of the code is entirely at your own risk and no warranty or guarantee is provided.

I am planning to develop the code further to tidy it up and support encryption of the embedded SQLite database, so if you have any comments or any improvements to the code I will gladly accept them and try to incorporate them into the code.

Tolon NoteKeeper 0.10 BETA Released

Splash screen for Tolon NoteKeeper 0.10 BETA

Tolon NoteKeeper 0.10 BETA is now available for download.

This is the first beta release in a long time and most of the changes are to cope with a new build environment and library updates. There are no real big feature changes, but I have tried to improve how it looks and have improved the reliability of the spell checking function.

Please try the new beta version if you can spare a few minutes, but before you do, please make a backup of your data.

This beta release can be downloaded from http://www.tolon.co.uk/software/notekeeper/download.

Best Regards,
Tolon.

NoteKeeper 0.10 Development Build 20120408

Tolon NoteKeeper 0.10 DEVELOPMENT build 20120408 is now available.

I am making the current development version of Tolon NoteKeeper 0.10 available for download whilst I do some testing.

Changes

New Features

  • 0.10 – New spell checking engine.
  • 0.10 – New toolbar icons.
  • 0.10 – New NoteKeeper icon.

Bug Fixes

  • 0.10 – Reduced build warnings.

Miscellaneous Changes

  • 0.10 – Added CRC checks for node data.
  • 0.10 – Now using MSVC9 compiler.
  • 0.10 – Now using paintlib 2.6.2

Download

This is a development build, so I must advise you to be very careful in using it with your existing data files. It should work ok, but always remember to save regularly and keep regular backups. You have been warned!

If you still fancy giving the new version a go, then by all means click the download link below. I would be most grateful if you could report any bugs, annoyances or general feedback to me.

The latest version can be downloaded from http://www.tolon.co.uk/software/notekeeper/download.

Best Regards,
Tolon.