v5 - added support for facility (point type 0x010903) and also now
catches and reports exceptions incurred during processing of attributes.

A facility point can have a mkgmap:xt-facilities attribute which is a
bitmask made from these values:

    0x000001 boat ramp
    0x000002 drinking water
    0x000004 restrooms
    0x000008 picnic area
    0x000010 campground
    0x000020 marina
    0x000040 fuel
    0x000080 marine supply
    0x000100 bait and tackle
    0x000200 groceries
    0x000400 restaurant
    0x000800 water/electric hook-up
    0x001000 boat/motor rental
    0x002000 guide service
    0x004000 lodging
    0x008000 dump station
    0x010000 handicap accessible

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

v4 - no functional change, just reworked slightly to improve efficiency.

I've tested this stuff with noddy examples but as I don't expect anyone
is in a great rush to use it big time, we will have to wait to see what
issues emerge (from the deep?)

Anyway, as all of this code should have no effect on map elements
without extended types, I reckon it should be safe to commit. So unless
anyone has any problems with that, I shall commit it later this week
and then at least it's there for people to dabble with.

If you do try it out, please report results.

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

v3 - more tweaks to lights + lines & areas

"morse" light type now takes the morse letter to flash, e.g. "morse A"

lights can be tagged as having a "racon" (you'll have to google it like
I did!) by setting mkgmap:xt-racon to yes.

Depth areas (type 0x0103xx) can have the depth attribute and lines
0x010105 to 0x010107 (contour, overhead cable, bridge) can have height.

There is a new mkgmap:xt-style attribute that takes a 16 bit value.
The bottom 4 bits specify a colour and bits 8-14 specify a line
style. See the cgpsmapper manual for the supported colours and styles.

As an example, a value of 0x3004 means red line like this +++++++

This can be used on lines with types 0x0104xx, 0x0105xx and 0x0106xx.

You can also use the xt-style (colour part only) attribute on
points of type 0x010500 which are labels with coloured text. The point's
name tag is shown on the map in the colour you specify. Could be useful.

There's still a bit more that could be done on on the light
flash sequence encoding but as it's rather unlikely that anyone will
every want to use that, I'm not bothering for the moment.

So, the job is pretty much done. As usual, all feedback is welcome.

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

v2 - moving swiftly on.

Added support for lights and reworked a few things.

mkgmap:xt-foundation-colour is now just mkgmap:xt-colour

mkgmap:xt-light-colour has been replaced with mkgmap:xt-light (more on
this below)

mkgmap:xt-light-type is now just mkgmap:xt-type

Light colour, range (in nm) and angle is now specified with the
mkgmap:xt-light tag - the values are just separated with commas, and
only the colour is required for simple lights. All angles are in
degrees. So, for example:

mkgmap:xt-light=red (a red light)
mkgmap:xt-light=red,5 (... visible at up to 5 nm)
mkgmap:xt-light=red,5,172 (... sector starts at 172 degrees)

The sector start angle only makes sense if you have multiple lights
defined (separate light defs with one of /;:) - for example:

mkgmap:xt-light=red,5,172/green,5,230/unlit,,300

you can add a text note, international designator or local designator
to a buoy or light with:

mkgmap:xt-note=hello world
mkgmap:xt-int-desig=ABC
mkgmap:xt-local-desig=XYZ

These show up in the POI properties window in mapsource.

There is also:

mkgmap:xt-height-above-foundation (a height)
mkgmap:xt-height-above-datum (a height)
mkgmap:xt-leading-angle (an angle)

you can now specify multiple period values to give a flash sequence
but, unfortunately, it only encodes 2 values at the moment, the
encoding for more than 2 values is yet to be discovered.

The full range of light colours is now supported:

        "unlit",
        "red",
        "green",
        "white",
        "blue",
        "yellow",
        "violet",
        "amber"

So, lights, buoys, obstructions are now fairly well catered for so it's
probably time to look at lines and areas.

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


Yo Landlubbers,

Here's the first cut at encoding support for extended type attributes.

This is mostly concerned with marine entities like (wreck depths, buoy
types, light types, colours, etc.) As there is quite a lot of it and I
haven't sussed it all out yet, I thought it would make sense to post
patches as I go along so that people can try it out and find the
bugs sooner rather than later.

The attribute values are supplied as special tags with a mkgmap:xt-
prefix. So, for example, mkgmap:xt-depth=40.6m specifies the depth of
something (in metres).

This first tranche supports:

Point types 0x0102xx (buoys/beacons) can have:
  mkgmap:xt-foundation-colour (a colour name from list FC below)
  mkgmap:xt-light-colour (a colour name from list LC below)
  mkgmap:xt-light-type (a type name from list LT below)
  mkgmap:xt-period (optional period in secs (not limited to integer)

Point types 0x0103xx (entities with depth/height) and 0x0104xx
(obstructions) mkgmap:xt-depth (a depth with optional units (ft or m)
default is m) mkgmap:xt-height (alias for, and mutually exclusive to,
xt-depth) mkgmap:xt-position (an optional position from list P below)

These attributes are turned into a sequence of bytes that are embodied
in the map object. For testing purposes you can also specify those bytes
explicitly as a sequence of hex digits using the mkgmap:xt-extra-bytes
tag but you won't want to use that unless you are working on attribute
value encoding (like me!)

Next up will be lights which I understand how to encode except for
multiple lights and angles - still working on that.

Then will come lines and areas.

All feedback welcome.

BTW a good source of info is the cgpsmapper manual which is readily
available on the net. It has a chapter on marine types.

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

List FC
                        "red",
                        "green",
                        "yellow",
                        "white",
                        "black",
                        "black-yellow",
                        "white-red",
                        "black-red",
                        "white-green",
                        "red-yellow",
                        "red-green",
                        "orange",
                        "black-yellow-black",
                        "yellow-black",
                        "yellow-black-yellow",
                        "red-white",
                        "green-red-green",
                        "red-green-red",
                        "black-red-black",
                        "yellow-red-yellow",
                        "green-red",
                        "black-white",
                        "white-orange",
                        "orange-white",
                        "green-white"

List LC
                        "unlit",
                        "red",
                        "green",
                        "white",

There is possibly some more light colours but I don't yet know how to
encode them so we should stick with these for the moment.

List LT
                        "fixed",
                        "isophase",
                        "flashing",
                        "group flashing",
                        "composite group flashing",
                        "occulting",
                        "group occulting",
                        "composite group occulting",
                        "long flashing",
                        "group long flashing",
                        "morse",
                        "quick",
                        "group quick",
                        "group quick and long flashing",
                        "interrupted quick",
                        "very quick",
                        "group very quick",
                        "group very quick and long flashing",
                        "interrupted very quick",
                        "ultra quick",
                        "interrupted ultra quick",
                        "fixed and occulting",
                        "fixed and group occulting",
                        "fixed and isophase",
                        "fixed and flashing",
                        "fixed and group flashing",
                        "fixed and long flashing",
                        "alternating",
                        "alternating occulting",
                        "alternating flashing",
                        "alternating group flashing"

The morse type takes a letter argument but I don't know how to encode
that yet.

 List P
                        "unknown",
                        "doubtful",
                        "existence doubtful",
                        "approximate",
                        "reported"
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/ExtTypeAttributes.java b/src/uk/me/parabola/imgfmt/app/trergn/ExtTypeAttributes.java
new file mode 100644
index 0000000..cee1917
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/trergn/ExtTypeAttributes.java
@@ -0,0 +1,850 @@
+/*
+ * Copyright (C) 2006 Steve Ratcliffe
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *
+ * Author: Steve Ratcliffe
+ * Create date: 12-Dec-2006
+ */
+package uk.me.parabola.imgfmt.app.trergn;
+
+import uk.me.parabola.imgfmt.app.Label;
+import uk.me.parabola.imgfmt.app.lbl.LBLFile;
+
+import uk.me.parabola.log.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.text.DecimalFormat;
+import java.text.ParsePosition;
+
+/*
+Add support for extended type attributes.
+
+These are nearly all for marine objects. Attribute values are supplied
+as tags with a mkgmap:xt- prefix. These tags are supported:
+
+mkgmap:xt-depth
+mkgmap:xt-height
+  value is distance with optional units suffix (ft or m)
+  applicable to points with types 0x0103xx and 0x0104xx and
+  lines with types 0x0103xx and 0x010105-0x010107 and areas
+  with types 0x0103xx.
+
+mkgmap:xt-style
+  value is 16 bit integer that specifies colour (lower 8 bits)
+  and line style (upper 8 bits) - applicable to lines with types
+  0x0104xx, 0x0105xx, 0x0106xx and points with type 0x010500 (colour
+  only).
+
+mkgmap:xt-colour
+  value is one of:
+	red
+	green
+	yellow
+	white
+	black
+	black-yellow
+	white-red
+	black-red
+	white-green
+	red-yellow
+	red-green
+	orange
+	black-yellow-black
+	yellow-black
+	yellow-black-yellow
+	red-white
+	green-red-green
+	red-green-red
+	black-red-black
+	yellow-red-yellow
+	green-red
+	black-white
+	white-orange
+	orange-white
+	green-white
+  applicable to points with type 0x0102xx (buoys) and it specifies the
+  foundation colour of the buoy.
+
+mkgmap:xt-type
+  value is one of:
+	fixed
+	isophase
+	flashing
+	group flashing
+	composite group flashing
+	occulting
+	group occulting
+	composite group occulting
+	long flashing
+	group long flashing
+	morse (followed by a letter)
+	quick
+	group quick
+	group quick and long flashing
+	interrupted quick
+	very quick
+	group very quick
+	group very quick and long flashing
+	interrupted very quick
+	ultra quick
+	interrupted ultra quick
+	fixed and occulting
+	fixed and group occulting
+	fixed and isophase
+	fixed and flashing
+	fixed and group flashing
+	fixed and long flashing
+	alternating
+	alternating occulting
+	alternating flashing
+	alternating group flashing
+  applicable to points with types 0x0101xx (lights) and 0x0102xx (buoys)
+  and it specifies the type of light
+
+mkgmap:xt-light
+  one or more light definitions (separated by ;: or /) - each light
+  definition is of the form colour,range,angle where colour is one
+  of:
+	unlit
+	red
+	green
+	white
+	blue
+	yellow
+	violet
+	amber
+  range is an optional number that specifies the visible range in nm -
+  angle is the start angle for the light (only makes sense when more than
+  one light is defined.
+
+mkgmap:xt-period
+  value is one or more period values (in seconds) (separated by commas) -
+  applicable to point types 0x0101xx (lights) and 0x0102xx (buoys).
+
+mkgmap:xt-racon
+  when set to yes/true/1 specifies object has a racon - applicable to
+  point types 0x0101xx (lights).
+
+mkgmap:xt-height-above-foundation
+mkgmap:xt-height-above-datum
+  value is distance with optional units (m or ft) - applicable to
+  point types 0x0101xx (lights).
+
+mkgmap:xt-leading-angle
+  value is a number - applicable to point types 0x0101xx (lights).
+
+mkgmap:xt-note
+mkgmap:xt-int-desig
+mkgmap:xt-local-desig
+  values are strings that are encoded as labels - applicable to
+  points of type 0x01xx (lights) and 0x0102xx (buoys).
+
+mkgmap:xt-facilities
+  value is a bitmask of facilities available:
+    0x000001 boat ramp
+    0x000002 drinking water
+    0x000004 restrooms
+    0x000008 picnic area
+    0x000010 campground
+    0x000020 marina
+    0x000040 fuel
+    0x000080 marine supply
+    0x000100 bait and tackle
+    0x000200 groceries
+    0x000400 restaurant
+    0x000800 water/electric hook-up
+    0x001000 boat/motor rental
+    0x002000 guide service
+    0x004000 lodging
+    0x008000 dump station
+    0x010000 handicap accessible
+  applicable only to points of type 0x010903 (facility)
+ */
+
+public class ExtTypeAttributes {
+
+	private static final Logger log = Logger.getLogger(ExtTypeAttributes.class);
+
+	private DecimalFormat decimalFormat = new DecimalFormat();
+
+	private Map<String, String> attributes;
+	private String objectName;
+
+	private byte[] extraBytes;
+
+	private Label note;
+	private Label intDesig;
+	private Label localDesig;
+
+	private Byte morseLetter;
+
+	private final int DISTANCE_FLAG_METRIC_INDEX = 0;
+	private final int DISTANCE_FLAG_TENTHS_INDEX = 1;
+
+	private final byte FLAGS0_RACON_BIT       = (1 << 0);
+	private final byte FLAGS0_NOTE_BIT        = (1 << 1);
+	private final byte FLAGS0_INT_DESIG_BIT   = (1 << 2);
+	private final byte FLAGS0_LOCAL_DESIG_BIT = (1 << 3);
+
+	public ExtTypeAttributes(Map<String, String> attributes, String objectName) {
+		this.attributes = attributes;
+		this.objectName = objectName;
+	}
+
+	public void processLabels(LBLFile lbl) {
+		if(note == null) {
+			String ns = attributes.get("note");
+			if(ns != null)
+				note = lbl.newLabel(ns);
+		}
+		if(intDesig == null) {
+			String ids = attributes.get("int-desig");
+			if(ids != null)
+				intDesig = lbl.newLabel(ids);
+		}
+		if(localDesig == null) {
+			String lds = attributes.get("local-desig");
+			if(lds != null)
+				localDesig = lbl.newLabel(lds);
+		}
+	}
+
+	protected byte[] getExtTypeExtraBytes(MapObject mapObject) {
+		try {
+			return encodeExtraBytes(mapObject);
+		}
+		catch (Exception e) {
+			log.error(objectName + " (" + e + ")");
+			return null;
+		}
+	}
+
+	private byte[] encodeExtraBytes(MapObject mapObject) {
+
+		// if we get called again, just return same result as before
+		if(extraBytes != null)
+			return extraBytes;
+
+		// first see if a string of raw hex digits has been supplied
+		// if so, use that and ignore everything else
+		String eb = attributes.get("extra-bytes");
+		if(eb != null) {
+			extraBytes = new byte[(eb.length() + 1) / 2];
+			for(int i = 0; i < eb.length(); ++i) {
+				int d = Integer.parseInt(eb.substring(i, i + 1), 16);
+				extraBytes[i / 2] |= (byte)(d << (4 * (1 - (i & 1))));
+			}
+			return extraBytes;
+		}
+
+		int type0to15 = mapObject.getType() & 0xffff;
+		int type8to15 = type0to15 & 0xff00;
+
+		if(mapObject instanceof Point) {
+
+			Light lights[] = parseLights(attributes.get("light"));
+			int[] periods = parsePeriods(attributes.get("period"));
+
+			if(type8to15 == 0x0100) { // lights
+				int nob = 6;
+				byte flags0 = 0;
+				byte flags1 = 0;
+				int lightType = lightType("");
+				if(meansYes(attributes.get("racon")))
+					flags0 |= FLAGS0_RACON_BIT;
+				if(note != null) {
+					nob += 3;
+					flags0 |= FLAGS0_NOTE_BIT;
+				}
+				if(intDesig != null) {
+					nob += 3;
+					flags0 |= FLAGS0_INT_DESIG_BIT;
+				}
+				if(localDesig != null) {
+					nob += 3;
+					flags0 |= FLAGS0_LOCAL_DESIG_BIT;
+				}
+				if(lights.length > 1) {
+					for(Light l : lights)
+						nob += (l.colour != 0)? 3 : 2;
+					flags1 |= 0x08; // multiple lights
+				}
+				if(periods.length > 1) {
+					for(int p : periods) {
+						while(p > 0x3f) {
+							++nob;
+							p -= 0x3f;
+						}
+						++nob;
+					}
+					flags1 |= 0x01; // further record present?
+				}
+				else if(morseLetter != null)
+					flags1 |= 0x01; // further record present?
+				byte lightsDef = 0x22;
+				String hafs = attributes.get("height-above-foundation");
+				boolean[] hafsDistFlags = new boolean[2];
+				Integer hafi = null;
+				if(hafs != null) {
+					hafi = parseDistance(hafs, hafsDistFlags);
+					if(hafsDistFlags[DISTANCE_FLAG_TENTHS_INDEX])
+						hafi /= 10;
+					nob += (hafi > 255)? 2 : 1;
+					if(hafi > 255)
+						lightsDef |= 0x80;
+					else
+						lightsDef |= 0x40;
+					if(!hafsDistFlags[DISTANCE_FLAG_METRIC_INDEX])
+						lightsDef &= ~0x20;
+				}
+				String hads = attributes.get("height-above-datum");
+				boolean[] hadsDistFlags = new boolean[2];
+				Integer hadi = null;
+				if(hads != null) {
+					hadi = parseDistance(hads, hadsDistFlags);
+					if(hadsDistFlags[DISTANCE_FLAG_TENTHS_INDEX])
+						hadi /= 10;
+					nob += (hadi > 255)? 2 : 1;
+					if(hadi > 255)
+						lightsDef |= 0x08;
+					else
+						lightsDef |= 0x04;
+					if(!hadsDistFlags[DISTANCE_FLAG_METRIC_INDEX])
+						lightsDef &= ~0x02;
+				}
+				String las = attributes.get("leading-angle");
+			    Integer leadingAngle = null;
+				if(las != null) {
+					leadingAngle = (int)(Double.parseDouble(las.trim()) * 10);
+					nob += 2;
+					flags1 |= 0x02; // leading angle present
+				}
+				extraBytes = new byte[nob + 2];
+				int i = 0;
+				extraBytes[i++] = (byte)(0xe0 | flags0);
+				extraBytes[i++] = (byte)((nob << 1) | 1); // bit0 always set?
+				extraBytes[i++] = (byte)(0x80 | lightType);
+				extraBytes[i++] = flags1;
+				extraBytes[i++] = lightsDef;
+				if(hafi != null) {
+					extraBytes[i++] = (byte)(int)hafi;
+					if(hafi > 255)
+						extraBytes[i++] = (byte)(hafi >> 8);
+				}
+				if(hadi != null) {
+					extraBytes[i++] = (byte)(int)hadi;
+					if(hadi > 255)
+						extraBytes[i++] = (byte)(hadi >> 8);
+				}
+				int period = 0;
+				for(int p : periods)
+					period += p;
+				extraBytes[i++] = (byte)period;
+				if(note != null) {
+					int off = note.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				if(localDesig != null) {
+					int off = localDesig.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				if(intDesig != null) {
+					int off = intDesig.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				if(leadingAngle != null) {
+					extraBytes[i++] = (byte)(int)leadingAngle;
+					extraBytes[i++] = (byte)(leadingAngle >> 8);
+				}
+				if(lights.length > 1) {
+					for(int l = 0; l < lights.length; ++l) {
+						int val = (lights[l].colour << 12) | (int)(lights[l].angle * 10);
+						if((l + 1) == lights.length)
+							val |= 0x8000;
+						extraBytes[i++] = (byte)val;
+						extraBytes[i++] = (byte)(val >> 8);
+						if(lights[l].colour != 0)
+							extraBytes[i++] = (byte)lights[l].range;
+					}
+				}
+				else {
+					int lc = 0;
+					int lr = 0;
+					if(lights.length > 0) {
+						lc = lights[0].colour;
+						lr = (int)lights[0].range & 0x1f;
+					}
+					extraBytes[i++] = (byte)((lc << 5) | lr);
+				}
+				if(periods.length > 1) {
+					if(periods.length > 2)
+						extraBytes[i++] = (byte)0x82;
+					else
+						extraBytes[i++] = (byte)0x81;
+					for(int p : periods) {
+						while(p > 0x3f) {
+							extraBytes[i++] = (byte)0x3f;
+							p -= 0x3f;
+						}
+						extraBytes[i++] = (byte)p;
+					}
+				}
+				else if(morseLetter != null)
+					extraBytes[i++] = morseLetter;
+				else
+					extraBytes[i++] = 0x01; // terminator?
+
+				return extraBytes;
+			}
+			else if(type8to15 == 0x0200) { // buoys
+				int nob = 4;
+				byte flags0 = 0;
+				byte flags1 = 0;
+				int lt = lightType("");
+				if(meansYes(attributes.get("racon"))) {
+					// this doesn't get reported on mapsource
+					// maybe racons aren't supported for buoys?
+					flags0 |= FLAGS0_RACON_BIT;
+				}
+				if(note != null) {
+					nob += 3;
+					flags0 |= FLAGS0_NOTE_BIT;
+				}
+				if(intDesig != null) {
+					nob += 3;
+					flags0 |= FLAGS0_INT_DESIG_BIT;
+				}
+				if(localDesig != null) {
+					nob += 3;
+					flags0 |= FLAGS0_LOCAL_DESIG_BIT;
+				}
+				if(periods.length > 0)
+					++nob;		// for total period
+				if(periods.length > 1) {
+					for(int p : periods) {
+						while(p > 0x3f) {
+							++nob;
+							p -= 0x3f;
+						}
+						++nob;
+					}
+					flags1 |= 0x02; // further record present?
+				}
+				else if(morseLetter != null)
+					flags1 |= 0x02; // further record present?
+				extraBytes = new byte[nob + 2];
+				int i = 0;
+				extraBytes[i++] = (byte)(0xe0 | flags0);
+				extraBytes[i++] = (byte)((nob << 1) | 1); // bit0 always set?
+				int lc = 0;
+				if(lights.length > 0) {
+					lc = lights[0].colour;
+				}
+				extraBytes[i++] = (byte)((lc << 6) | colour(""));
+				flags1 |= (byte)((lc >> 2) & 1); // bit 0 is MSB of light colour
+				extraBytes[i++] = flags1;
+				if(note != null) {
+					int off = note.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				if(localDesig != null) {
+					int off = localDesig.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				if(intDesig != null) {
+					int off = intDesig.getOffset();
+					extraBytes[i++] = (byte)off;
+					extraBytes[i++] = (byte)(off >> 8);
+					extraBytes[i++] = (byte)(off >> 16);
+				}
+				byte flags2 = 0;
+				if(periods.length > 0)
+					flags2 |= (byte)0x80;
+				extraBytes[i++] = (byte)(flags2 | lt);
+				if(periods.length > 0) {
+					int period = 0;
+					for(int p : periods)
+						period += p;
+					extraBytes[i++] = (byte)period;
+					if(periods.length > 1) {
+						if(periods.length > 2)
+							extraBytes[i++] = (byte)0x82;
+						else
+							extraBytes[i++] = (byte)0x81;
+						for(int p : periods) {
+							while(p > 0x3f) {
+								extraBytes[i++] = (byte)0x3f;
+								p -= 0x3f;
+							}
+							extraBytes[i++] = (byte)p;
+						}
+					}
+					else
+						extraBytes[i++] = 0x01; // terminator?
+				}
+				else if(morseLetter != null)
+					extraBytes[i++] = morseLetter;
+				else
+					extraBytes[i++] = 0x01; // terminator?
+
+				return extraBytes;
+			}
+			else if(type8to15 == 0x0300 || // things with depth/height
+					type8to15 == 0x0400) { // obstructions
+				String ds = attributes.get("depth");
+				if(ds == null)
+					ds = attributes.get("height");
+				if(ds != null) {
+					boolean[] distFlags = new boolean[2];
+					Integer di = parseDistance(ds, distFlags);
+					if(di != null) {
+						if(di > 255) {
+							extraBytes = new byte[3];
+							extraBytes[0] = (byte)0xa0;
+						}
+						else {
+							extraBytes = new byte[2];
+							extraBytes[0] = (byte)0x80;
+						}
+						if(distFlags[DISTANCE_FLAG_METRIC_INDEX])
+							extraBytes[0] |= 0x10;
+						if(distFlags[DISTANCE_FLAG_TENTHS_INDEX])
+							extraBytes[0] |= 0x08;
+						if(type8to15 == 0x0400) { // obstructions
+							extraBytes[0] |= position();
+						}
+						extraBytes[1] = (byte)(int)di;
+						if(di > 255)
+							extraBytes[2] = (byte)(di >> 8);
+
+						return extraBytes;
+					}
+				}
+			}
+			else if(type8to15 == 0x0500) { // label
+				String ss = attributes.get("style");
+				if(ss != null) {
+					int style = Integer.decode(ss.trim());
+					// format is 0xCC (CC = colour)
+					extraBytes = new byte[1];
+					extraBytes[0] = (byte)(style & 0xf);
+					return extraBytes;
+				}
+			}
+			else if(type0to15 == 0x0903) { // facility
+				String fs = attributes.get("facilities");
+				if(fs != null) {
+					int facilities = Integer.decode(fs.trim());
+					extraBytes = new byte[3];
+					extraBytes[0] = (byte)(0xa0 | (facilities & 0x1f));
+					extraBytes[1] = (byte)(facilities >> 5);
+					extraBytes[2] = (byte)(((facilities >> 13) & 0x07) |
+										   ((facilities >> 12) & 0x18));
+					return extraBytes;
+				}
+			}
+		}
+		else if(mapObject instanceof Polyline) {
+
+			if(type8to15 == 0x0300 ||	// depth areas
+			   (!(mapObject instanceof Polygon) &&
+				(type0to15 == 0x0105 || // contour line
+				 type0to15 == 0x0106 || // overhead cable
+				 type0to15 == 0x0107))) { // bridge
+				String ds = attributes.get("depth");
+				if(ds == null)
+					ds = attributes.get("height");
+				if(ds != null) {
+					boolean[] distFlags = new boolean[2];
+					Integer di = parseDistance(ds, distFlags);
+					if(di != null) {
+						if(di > 255) {
+							extraBytes = new byte[3];
+							extraBytes[0] = (byte)0xa0;
+						}
+						else {
+							extraBytes = new byte[2];
+							extraBytes[0] = (byte)0x80;
+						}
+						if(distFlags[DISTANCE_FLAG_METRIC_INDEX])
+							extraBytes[0] |= 0x10;
+						if(distFlags[DISTANCE_FLAG_TENTHS_INDEX])
+							extraBytes[0] |= 0x08;
+						if(type8to15 == 0x04) { // obstructions
+							extraBytes[0] |= position();
+						}
+						extraBytes[1] = (byte)(int)di;
+						if(di > 255)
+							extraBytes[2] = (byte)(di >> 8);
+
+						return extraBytes;
+					}
+				}
+			}
+			else if(!(mapObject instanceof Polygon) &&
+					(type8to15 == 0x0400 || // various lines
+					 type8to15 == 0x0500 ||
+					 type8to15 == 0x0600)) {
+				String ss = attributes.get("style");
+				if(ss != null) {
+					int style = Integer.decode(ss.trim());
+					if((style & 0xff00) != 0) {
+						// format is 0xSSCC (SS = style, CC = colour)
+						extraBytes = new byte[2];
+						extraBytes[0] = (byte)(0x80 | (style & 0xf));
+						extraBytes[1] = (byte)(((style >> 9) & 0x18) | ((style >> 8) & 0x3));
+					}
+					else {
+						// format is 0xCC (CC = colour)
+						extraBytes = new byte[1];
+						extraBytes[0] = (byte)(style & 0xf);
+					}
+					return extraBytes;
+				}
+			}
+		}
+
+		return null;
+	}
+
+	private boolean meansYes(String s) {
+		if(s == null)
+			return false;
+		s = s.toLowerCase();
+		return ("yes".startsWith(s) || "true".startsWith(s) || "1".equals(s));
+	}
+
+	private Integer parseDistance(String ds, boolean[] flags) {
+		ParsePosition pp = new ParsePosition(0);
+		Number dn = decimalFormat.parse(ds, pp);
+		if(dn != null) {
+			double dd = dn.doubleValue();
+			int di = dn.intValue();
+			flags[DISTANCE_FLAG_METRIC_INDEX] = true;
+			flags[DISTANCE_FLAG_TENTHS_INDEX] = false;
+			if("ft".equals(ds.substring(pp.getIndex()).trim().toLowerCase()))
+				flags[DISTANCE_FLAG_METRIC_INDEX] = false;
+			if((double)di != dd) {
+				// number has fractional part
+				di = (int)(dd * 10);
+				flags[DISTANCE_FLAG_TENTHS_INDEX] = true;
+			}
+
+			return di;
+		}
+
+		return null;
+	}
+
+	private int colour(String prefix) {
+
+		String c = attributes.get(prefix + "colour");
+		if(c == null)
+			c = attributes.get(prefix + "color");
+		if(c == null)
+			return 0;
+
+		String[] colours = {
+			"",
+			"red",
+			"green",
+			"yellow",
+			"white",
+			"black",
+			"black-yellow",
+			"white-red",
+			"black-red",
+			"white-green",
+			"red-yellow",
+			"red-green",
+			"orange",
+			"black-yellow-black",
+			"yellow-black",
+			"yellow-black-yellow",
+			"red-white",
+			"green-red-green",
+			"red-green-red",
+			"black-red-black",
+			"yellow-red-yellow",
+			"green-red",
+			"black-white",
+			"white-orange",
+			"orange-white",
+			"green-white"
+		};
+
+		c = c.toLowerCase();
+
+		for(int i = 0; i < colours.length; ++i)
+			if(colours[i].equals(c))
+				return i;
+
+		return 0;
+	}
+
+	private int lightType(String prefix) {
+		String lt = attributes.get(prefix + "type");
+		if(lt == null)
+			return 0;
+
+		String[] types = {
+			"",
+			"fixed",
+			"isophase",
+			"flashing",
+			"group flashing",
+			"composite group flashing",
+			"occulting",
+			"group occulting",
+			"composite group occulting",
+			"long flashing",
+			"group long flashing",
+			"morse",
+			"quick",
+			"group quick",
+			"group quick and long flashing",
+			"interrupted quick",
+			"very quick",
+			"group very quick",
+			"group very quick and long flashing",
+			"interrupted very quick",
+			"ultra quick",
+			"interrupted ultra quick",
+			"fixed and occulting",
+			"fixed and group occulting",
+			"fixed and isophase",
+			"fixed and flashing",
+			"fixed and group flashing",
+			"fixed and long flashing",
+			"alternating",
+			"alternating occulting",
+			"alternating flashing",
+			"alternating group flashing"
+		};
+
+		if(lt.toLowerCase().startsWith("morse")) {
+			String ml = lt.substring(5).trim();
+			if(ml.length() > 0) {
+				morseLetter = (byte)ml.charAt(0);
+				lt = "morse";
+			}
+		}
+
+		lt = lt.toLowerCase();
+
+		for(int i = 0; i < types.length; ++i)
+			if(types[i].equals(lt))
+				return i;
+
+		return 0;
+	}
+
+	private int position() {
+		String ps = attributes.get("position");
+		if(ps == null)
+			return 0;
+
+		String[] positions = {
+			"unknown",
+			"",
+			"doubtful",
+			"existence doubtful",
+			"approximate",
+			"reported"
+		};
+
+		ps = ps.toLowerCase();
+
+		for(int i = 0; i < positions.length; ++i)
+			if(positions[i].equals(ps))
+				return i;
+
+		return 0;
+	}
+
+	private int[] parsePeriods(String ps) {
+		if(ps == null)
+			return new int[0];
+		String [] psa = ps.split(",");
+		int [] periods = new int[psa.length];
+		for(int i = 0; i < psa.length; ++i)
+			periods[i] = (int)(Double.parseDouble(psa[i].trim()) * 10);
+		return periods;
+	}
+
+	private Light[] parseLights(String ls) {
+		if(ls == null)
+			return new Light[0];
+		String[] defs = ls.split("[:;/]");
+		Light[] lights = new Light[defs.length];
+		for(int i = 0; i < defs.length; ++i) {
+			String def = defs[i].trim();
+			if(def.length() > 0)
+				lights[i] = new Light(def);
+		}
+		return lights;
+	}
+
+	class Light {
+		private int    colour;
+		private double range;
+		private double angle;
+
+		String[] colours = {
+			"unlit",
+			"red",
+			"green",
+			"white",
+			"blue",
+			"yellow",
+			"violet",
+			"amber"
+		};
+
+		public Light(String desc) {
+			String[] parts = desc.split(",");
+			if(parts.length > 0) {
+				String lc = parts[0].toLowerCase();
+				for(int i = 0; i < colours.length; ++i) {
+					if(colours[i].equals(lc)) {
+						colour = i;
+						break;
+					}
+				}
+			}
+			if(parts.length > 1 && colour != 0)
+				range = Double.parseDouble(parts[1]);
+			if(parts.length > 2)
+				angle = Double.parseDouble(parts[2]);
+
+			//System.err.println("light = " + this);
+		}
+
+		public String toString() {
+			return "(" + colours[colour] + "," + range + "," + angle + ")";
+		}
+	}
+}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java b/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
index 142f859..bbbd411 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
@@ -55,6 +55,8 @@ public abstract class MapObject {
 	private int deltaLong;
 	private int deltaLat;
 
+	private ExtTypeAttributes extTypeAttributes;
+
 	/**
 	 * Write this object to the given file.
 	 *
@@ -147,4 +149,12 @@ public abstract class MapObject {
 	public List<Label> getRefLabels() {
 		return refLabels;
 	}
+
+	protected byte[] getExtTypeExtraBytes() {
+		return (extTypeAttributes != null)? extTypeAttributes.getExtTypeExtraBytes(this) : null;
+	}
+
+	public void setExtTypeAttributes(ExtTypeAttributes eta) {
+		extTypeAttributes = eta;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Point.java b/src/uk/me/parabola/imgfmt/app/trergn/Point.java
index 84b6434..65f48f7 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Point.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Point.java
@@ -83,6 +83,7 @@ public class Point extends MapObject {
 		assert hasExtendedType();
 		int type = getType();
 		int labelOff = getLabel().getOffset();
+		byte[] extraBytes = getExtTypeExtraBytes();
 
 		if (poi != null) {
 			labelOff = poi.getOffset();
@@ -90,9 +91,11 @@ public class Point extends MapObject {
 		}
 		if(labelOff != 0)
 			type |= 0x20;		// has label
-
+		if(extraBytes != null)
+			type |= 0x80;		// has extra bytes
 		stream.write(type >> 8);
 		stream.write(type);
+
 		int deltaLong = getDeltaLong();
 		int deltaLat = getDeltaLat();
 		stream.write(deltaLong);
@@ -105,7 +108,9 @@ public class Point extends MapObject {
 			stream.write(labelOff >> 8);
 			stream.write(labelOff >> 16);
 		}
-		// FIXME - extra bytes?
+
+		if(extraBytes != null)
+			stream.write(extraBytes);
 	}
 
 	public void setPOIRecord(POIRecord poirecord) {
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
index 64d7faa..b52774c 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
@@ -147,9 +147,12 @@ public class Polyline extends MapObject {
 		assert hasExtendedType();
 		int type = getType();
 		int labelOff = getLabel().getOffset();
+		byte[] extraBytes = getExtTypeExtraBytes();
 
 		if(labelOff != 0)
 			type |= 0x20;		// has label
+		if(extraBytes != null)
+			type |= 0x80;		// has extra bytes
 		stream.write(type >> 8);
 		stream.write(type);
 
@@ -182,7 +185,9 @@ public class Polyline extends MapObject {
 			stream.write(labelOff >> 8);
 			stream.write(labelOff >> 16);
 		}
-		// FIXME - extra bytes?
+
+		if(extraBytes != null)
+			stream.write(extraBytes);
 	}
 
 	public void addCoord(Coord co) {
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index efe7899..1f2b889 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -37,6 +37,7 @@ import uk.me.parabola.imgfmt.app.map.Map;
 import uk.me.parabola.imgfmt.app.net.NETFile;
 import uk.me.parabola.imgfmt.app.net.NODFile;
 import uk.me.parabola.imgfmt.app.net.RoadDef;
+import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 import uk.me.parabola.imgfmt.app.trergn.Point;
 import uk.me.parabola.imgfmt.app.trergn.PointOverview;
@@ -309,7 +310,7 @@ public class MapBuilder implements Configurable {
 			if(p.isExit()) {
 				processExit(map, (MapExitPoint)p);
 			}
-			else if(!p.isCity() && (p.isRoadNamePOI() || poiAddresses)) {
+			else if(!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses)) {
 				boolean doAutofill;
 				if(locationAutofillLevel > 0 || p.isRoadNamePOI())
 					doAutofill = true;
@@ -738,6 +739,12 @@ public class MapBuilder implements Configurable {
 			Point p = div.createPoint(name);
 			p.setType(point.getType());
 
+			if(point.hasExtendedType()) {
+				ExtTypeAttributes eta = point.getExtTypeAttributes();
+				eta.processLabels(lbl);
+				p.setExtTypeAttributes(eta);
+			}
+
 			Coord coord = point.getLocation();
 			p.setLatitude(coord.getLatitude());
 			p.setLongitude(coord.getLongitude());
@@ -952,6 +959,12 @@ public class MapBuilder implements Configurable {
 			Polyline pl = div.createLine(line.getName(), line.getRef());
 			if(!element.hasExtendedType())
 				div.setPolylineNumber(pl);
+			else {
+				ExtTypeAttributes eta = element.getExtTypeAttributes();
+				eta.processLabels(map.getLblFile());
+				pl.setExtTypeAttributes(eta);
+			}
+
 			pl.setDirection(line.isDirection());
 
 			for (Coord co : line.getPoints())
@@ -991,6 +1004,11 @@ public class MapBuilder implements Configurable {
 				pg.addCoord(co);
 
 			pg.setType(shape.getType());
+			if(element.hasExtendedType()) {
+				ExtTypeAttributes eta = element.getExtTypeAttributes();
+				eta.processLabels(map.getLblFile());
+				pg.setExtTypeAttributes(eta);
+			}
 			map.addMapObject(pg);
 		}
 	}
diff --git a/src/uk/me/parabola/mkgmap/general/MapElement.java b/src/uk/me/parabola/mkgmap/general/MapElement.java
index c6c2abd..240216b 100644
--- a/src/uk/me/parabola/mkgmap/general/MapElement.java
+++ b/src/uk/me/parabola/mkgmap/general/MapElement.java
@@ -19,6 +19,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 
 /**
  * A map element is a point, line or shape that appears on the map.  This
@@ -33,11 +34,8 @@ public abstract class MapElement {
 
 	private int minResolution = 24;
 	private int maxResolution = 24;
-	
-	private String zipCode;
-	private String city;
-	private String region;
-	private String country;	
+
+	private ExtTypeAttributes extTypeAttributes;
 	
 	private final Map<String, String> attributes = new HashMap<String, String>();
 
@@ -50,6 +48,7 @@ public abstract class MapElement {
 		type = orig.type;
 		minResolution = orig.minResolution;
 		maxResolution = orig.maxResolution;
+		extTypeAttributes = orig.extTypeAttributes;
 	}
 
 	/**
@@ -76,36 +75,44 @@ public abstract class MapElement {
 		this.ref = ref;
 	}
 
+	public ExtTypeAttributes getExtTypeAttributes() {
+		return extTypeAttributes;
+	}
+
+	public void setExtTypeAttributes(ExtTypeAttributes eta) {
+		extTypeAttributes = eta;
+	}
+
 	public String getCity() {
-		return city;
+		return attributes.get("city");
 	}
 
 	public void setCity(String city) {
-		this.city = city;
+		attributes.put("city", city);
 	}
 	
 	public String getZip() {
-		return zipCode;
+		return attributes.get("zip");
 	}
 
 	public void setZip(String zip) {
-		this.zipCode = zip;
+		attributes.put("zip", zip);
 	}
 
 	public String getCountry() {
-		return country;
+		return attributes.get("country");
 	}
 
 	public void setCountry(String country) {
-		this.country = country;
+		attributes.put("country", country);
 	}
 	
 	public String getRegion() {
-		return region;
+		return attributes.get("region");
 	}
 
 	public void setRegion(String region) {
-  		this.region = region;
+		attributes.put("region", region);
 	}	
 	
 	public String getStreet() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index e3b7d32..c45f2d9 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -31,6 +31,7 @@ import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
 import uk.me.parabola.imgfmt.app.Exit;
+import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.AreaClipper;
 import uk.me.parabola.mkgmap.general.Clipper;
@@ -509,6 +510,9 @@ public class StyledConverter implements OsmConverter {
 
 		if(region != null)
 		  ms.setRegion(region);			
+
+		if(MapElement.hasExtendedType(gt.getType()))
+			ms.setExtTypeAttributes(new ExtTypeAttributes(element.getTagsWithPrefix("mkgmap:xt-", true), "OSM id " + element.getId()));
 	}
 
 	void addRoad(Way way, GType gt) {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
index a49e7c7..c7dc51b 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
@@ -17,6 +17,7 @@ package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Superclass of the node, segment and way OSM elements.
@@ -97,4 +98,8 @@ public abstract class Element implements Iterable<String> {
 		if (this.name == null)
 			this.name = name;
 	}
+
+	public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) {
+		return tags.getTagsWithPrefix(prefix, removePrefix);
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
index e312316..57d2ba6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
@@ -16,7 +16,9 @@
  */
 package uk.me.parabola.mkgmap.reader.osm;
 
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Store the tags that belong to an Element.
@@ -271,4 +273,20 @@ public class Tags implements Iterable<String> {
 					put(e.key, e.value);
 		}
 	}
+
+	public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) {
+		Map<String, String> map = new HashMap<String, String>();
+
+		int prefixLen = prefix.length();
+		for(int i = 0; i < capacity; ++i) {
+			if(keys[i] != null && keys[i].startsWith(prefix)) {
+				if(removePrefix)
+					map.put(keys[i].substring(prefixLen), values[i]);
+				else
+					map.put(keys[i], values[i]);
+			}
+		}
+
+		return map;
+	}
 }
_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to