Index: src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java	(revision 4900)
+++ src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java	(working copy)
@@ -113,6 +113,7 @@
 	
 	private static final Pattern KEY_SPLITTER = Pattern.compile(Pattern.quote("_"));
 	private static final Pattern SEMICOLON_SPLITTER = Pattern.compile(Pattern.quote(";"));
+	private static final short NATURAL_TAG_KEY = TagDict.getInstance().xlate("natural");
 
 	/**
 	 * When order-by-decreasing-area we need all bit of sea to be output consistently.
@@ -498,48 +499,22 @@
 	 */
 	@Override
 	public void onAddWay(Way way) {
-		String natural = way.getTag("natural");
+		String natural = way.getTag(NATURAL_TAG_KEY);
 		if (natural == null)
 			return;
-	
-		// cope with compound tag value
-		StringBuilder others = null;
-		boolean foundCoastline = false;
-		for (String n : SEMICOLON_SPLITTER.split(natural)) {
-			if ("coastline".equals(n.trim()))
-				foundCoastline = true;
-			else if (others == null)
-				others = new StringBuilder(n);
-			else
-				others.append(';').append(n);
-		}
-		if (!foundCoastline)
+		int posn = natural.indexOf("coastline");
+		if (posn < 0)
 			return;
+		if (posn > 0 && natural.charAt(posn-1) != ';')
+			return;
+		if (natural.length() > posn+9 && natural.charAt(posn+9) != ';')
+			return;
 		if (precompSea != null)
 			splitCoastLineToLineAndShape(way, natural);
 		else if (coastlineFilenames == null) {
-			/* RWB ???
-			 *
-			 * I'd have thought it better to leave the original way, which has been saved,
-			 * untouched. The copy doesn't need any tags at this point. Later it might
-			 * be made into a polygon and tagged as land or sea.
-			 *
-			 * Could do a couple of quick check here to save effort later:
-			 * 1/ if no part in tile then stop, don't change anything or save.
-			 * 2/ if closed(), add to island list instead of shoreline. Any single closed
-			 *    way will be a small island, not a sea! Later, after shoreline
-			 *    has been merged/clipped etc, check these again for clipping and add clippings
-			 *    to shoreline and unclipped back into islands
-			 */
-			// create copy of way that has only the natural=coastline tag
+			// create copy of way that will become (part of) land/sea polygon
 			Way shore = new Way(way.getOriginalId(), way.getPoints());
 			shore.markAsGeneratedFrom(way);
-			shore.addTag("natural", "coastline");
-			saver.addWay(shore);
-			
-			way.deleteTag("natural");
-			if (others != null)
-				way.addTag("natural", others.toString());
 			shoreline.add(way);
 		}
 	}
@@ -553,8 +528,6 @@
 	 * @param naturalVal the tag value
 	 */
 	private void splitCoastLineToLineAndShape(Way way, String naturalVal){
-		if (precompSea == null)
-			return;
 		if (way.hasIdenticalEndPoints()){
 			// add a copy of this way to be able to draw it as a shape
 			Way shapeWay = new Way(way.getOriginalId(), way.getPoints());
@@ -561,7 +534,7 @@
 			shapeWay.markAsGeneratedFrom(way);
 			shapeWay.copyTags(way);
 			// change the tag so that only special rules looking for it are firing
-			shapeWay.deleteTag("natural"); 
+			shapeWay.deleteTag(NATURAL_TAG_KEY);
 			shapeWay.addTag("mkgmap:removed_natural",naturalVal); 
 			// tag that this way so that it is used as shape only
 			shapeWay.addTag(MultiPolygonRelation.STYLE_FILTER_TAG, MultiPolygonRelation.STYLE_FILTER_POLYGON);
@@ -695,7 +668,7 @@
 		boolean changeLandTag = landTag != null && ("natural".equals(landTag[0]) && !"land".equals(landTag[1]));
 		for (Way w : landWays) {
 			if (changeLandTag) {
-				w.deleteTag("natural");
+				w.deleteTag(NATURAL_TAG_KEY);
 				w.addTag(landTag[0], landTag[1]);
 			}
 			saver.addWay(w);
@@ -842,7 +815,7 @@
 				if (entry != null) {
 					is = pd.zipFile.getInputStream(entry);
 				} else {
-					log.error("Preompiled sea tile " + tileName + " not found.");
+					log.error("Precompiled sea tile " + tileName + " not found.");
 				}
 			} else {
 				File precompTile = new File(pd.dirFile, tileName);
@@ -873,7 +846,7 @@
 					// interfere with the ids of this run
 					w.markAsGeneratedFrom(w);
 
-					if ("land".equals(w.getTag("natural"))) {
+					if ("land".equals(w.getTag(NATURAL_TAG_KEY))) {
 						landWays.add(w);
 					} else {
 						seaWays.add(w);
@@ -937,7 +910,7 @@
 			List<List<Coord>> shapes = Java2DConverter.areaToShapes(area, commonCoordMap);
 			for (List<Coord> points : shapes) {
 				Way w = new Way(FakeIdGenerator.makeFakeId(), points);
-				w.addTag("natural", type);
+				w.addTag(NATURAL_TAG_KEY, type);
 				ways.add(w);
 			}
 		}
@@ -1045,10 +1018,13 @@
 			return;
 		}
 
-		Relation seaRelation = null;
-		
 		// handle islands (closed shoreline components) first (they're easy)
 		handleIslands();
+		if (maxCoastlineGap > 0) {
+			if (closeGaps()) { // there may be more islands now
+				handleIslands();
+			}
+		}
 
 		if (islands.isEmpty()) {
 			// the tile doesn't contain any islands so we can assume
@@ -1060,41 +1036,32 @@
 
 		// the remaining shoreline segments should intersect the boundary
 		// find the intersection points and store them in a SortedMap
-		NavigableMap<Double, Way> hitMap = findIntesectionPoints();
+		NavigableMap<Double, Way> hitMap = findIntersectionPoints();
 		verifyHits(hitMap);
+		NavigableMap<Double, Way> copyHitMap = new TreeMap<>(hitMap);
+		// generate background polygons (sea & land) that cover complete area and touch the edge
+		List<Way> seaAreas = createSeaPolygons(hitMap);
+		List<Way> landAreas = createLandPolygons(copyHitMap);
 
-		if (generateSeaBackground) {
-			// the background is sea so all anti-islands should be
-			// contained by land otherwise they won't be visible
-			if (generateSeaUsingMP) {
-				long multiId = FakeIdGenerator.makeFakeId();
-				log.debug("Generate seabounds relation", multiId);
-				seaRelation = new GeneralRelation(multiId);
-				seaRelation.addTag("type", "multipolygon");
-				seaRelation.addTag("natural", "sea");
-			}
+		Relation seaRelation = null;
+		if (generateSeaUsingMP && generateSeaBackground) { // use multipolygon to cut out islands from sea
+			// generateSeaBackground is now a bit of a mismomer and really means "there are islands"
+			long multiId = FakeIdGenerator.makeFakeId();
+			log.debug("Generate seabounds relation", multiId);
+			seaRelation = new GeneralRelation(multiId);
+			seaRelation.addTag("type", "multipolygon");
+			seaRelation.addTag(NATURAL_TAG_KEY, "sea");
+		}
 
-			createLandPolygons(hitMap);
-			processIslands(seaRelation);
-			checkIslands(true);
+		processSeaAreas(seaAreas, seaRelation); // seaAreas meet the edge of the tile
+		processLandAreas(landAreas, null); // landAreas meet the edge if the tile
+		processSeaAreas(antiIslands, seaRelation); // antiIslands are seas that don't touch the edge
+		processLandAreas(islands, seaRelation); // islands don't touch the edge
 
-			Way sea = createSeaWay(true);
-
-			log.info("sea:", sea);
-			saver.addWay(sea);
-			if(seaRelation != null)
-				seaRelation.addElement("outer", sea);
-		} else {
-			// background is land
-			createSeaPolygons(hitMap);
-			checkIslands(false);
-			
-			// generate a land polygon so that the tile's
-			// background colour will match the land colour on the
-			// tiles that do contain some sea
-			Way land = createLandWay();
-			saver.addWay(land);
-			log.info("land:", land);
+		if (checkCoastline) {
+			islands.addAll(landAreas);
+			antiIslands.addAll(seaAreas);
+			checkIslands(generateSeaBackground);
 		}
 
 		if (seaRelation != null) {
@@ -1110,7 +1077,7 @@
 			}
 			saver.addRelation(coastRel);
 		}
-		
+
 		shoreline = null;
 		islands = null;
 		antiIslands = null;
@@ -1118,25 +1085,44 @@
 
 	/**
 	 * These are bit of land that have been generated as polygons
+	 * @param areas list of land polygons
 	 * @param seaRelation if set, add as inner
 	 */
-	private void processIslands(Relation seaRelation) {
-		for (Way w : islands) {
+	private void processLandAreas(List<Way> areas, Relation seaRelation) {
+		for (Way w : areas) {
+			w.addTag(landTag[0], landTag[1]);
+			//log.info("adding land", land);
 			if (seaRelation != null) {
 				// create a "inner" way for each island
 				seaRelation.addElement("inner", w);
 			}
+			saver.addWay(w);
 		}
 	}
 
 	/**
+	 * These are bits of sea have been generated as polygons.
+	 * @param areas list of sea polygons
+	 * @param seaRelation if set, add as inner
+	 */
+	private void processSeaAreas(List<Way> areas, Relation seaRelation) {
+		for (Way w : areas) {
+			//log.info("adding sea", w);
+			w.setFullArea(SEA_SIZE);
+			if (seaRelation != null) {
+				seaRelation.addElement("outer", w);
+			} else {
+				w.addTag(NATURAL_TAG_KEY, "sea");
+				saver.addWay(w);
+			}
+		}
+	}
+
+	/**
 	 * Check whether land is enclosed in land or sea within sea.
 	 * @param seaBased true if the tile is also sea with land [multi-]polygons
 	 */
 	private void checkIslands(boolean seaBased) {
-		if (!checkCoastline)
-			return;
-		
 		for (Way ai : antiIslands) {
 			Way containingLand = null;
 			Way containingSea = null;
@@ -1216,7 +1202,7 @@
 		}
 		Way sea = new Way(seaId, bbox.toCoords());
 		sea.reverse(); // make clockwise for consistency
-		sea.addTag("natural", "sea");
+		sea.addTag(NATURAL_TAG_KEY, "sea");
 		sea.setFullArea(SEA_SIZE);
 		return sea;
 	}
@@ -1255,31 +1241,19 @@
 		while (it.hasNext()) {
 			Way w = it.next();
 			if (w.hasIdenticalEndPoints()) {
-				addClosedShore(w);
+				if (Way.clockwise(w.getPoints()))
+					antiIslands.add(w);
+				else
+					islands.add(w);
 				it.remove();
 			}
 		}
-
-		closeGaps();
-		// there may be more islands now
-		it = shoreline.iterator();
-		while (it.hasNext()) {
-			Way w = it.next();
-			if (w.hasIdenticalEndPoints()) {
-				log.debug("closed after concatenating", w);
-				addClosedShore(w);
-				it.remove();
-			}
-		}
 	}
 
-	private void closeGaps() {
-		if (maxCoastlineGap <= 0)
-			return;
-	
+	private boolean closeGaps() {
 		// join up coastline segments whose end points are less than 
 		// maxCoastlineGap metres apart
-		boolean changed;
+		boolean someClosed = false, changed;
 		do {
 			changed = false;
 			Iterator<Way> iter = shoreline.iterator();
@@ -1293,10 +1267,12 @@
 					if (closed != null) {
 						saver.addWay(closed);
 						changed = true;
+						someClosed = true;
 					}
 				}
 			}
 		} while (changed);
+		return someClosed;
 	}
 	
 	private Way tryCloseGap(Way w1) {
@@ -1334,7 +1310,7 @@
 			shoreline.remove(nearest);
 			// make a line that shows the filled gap
 			Way w = new Way(FakeIdGenerator.makeFakeId());
-			w.addTag("natural", "mkgmap:coastline-gap");
+			w.addTag(NATURAL_TAG_KEY, "mkgmap:coastline-gap");
 			w.addPoint(w1e);
 			w.addPoint(w2s);
 			return w;
@@ -1342,35 +1318,14 @@
 		return null;
 	}
 
-	private void addClosedShore(Way w) {
-		if (Way.clockwise(w.getPoints()))
-			addAsSea(w);
-		else
-			addAsLand(w);
-	}
-
-	private void addAsSea(Way w) {
-		w.addTag("natural", "sea");
-		log.info("adding anti-island", w);
-		antiIslands.add(w);
-		w.setFullArea(SEA_SIZE);
-		saver.addWay(w);
-	}
-
-	private void addAsLand(Way w) {
-		w.addTag(landTag[0], landTag[1]);
-		log.info("adding island", w);
-		islands.add(w);
-		saver.addWay(w);
-	}
-
 	/**
 	 * Add lines to ways that touch or cross the sea bounds so that the way is closed along the edges of the bounds. 
 	 * Adds complete edges or parts of them. This is done counter-clockwise.   
 	 * @param hitMap A map of the 'hits' where the shore line intersects the boundary.  
 	 */
-	private void createLandPolygons(NavigableMap<Double, Way> hitMap) {
+	private List<Way> createLandPolygons(NavigableMap<Double, Way> hitMap) {
 		NavigableSet<Double> hits = hitMap.navigableKeySet();
+		List<Way> areas = new ArrayList<>();
 		while (!hits.isEmpty()) {
 			Double hFirst = hits.first();
 			Double hStart = hFirst, hEnd;
@@ -1398,8 +1353,9 @@
 			} while (!finished);
 			w.addPoint(w.getFirstPoint()); // close shape
 			log.info("adding landPoly, hits.size()", hits.size());
-			addAsLand(w);
+			areas.add(w);
 		}
+		return areas;
 	}
 
 	/**
@@ -1408,8 +1364,9 @@
 	 * This is much the same as createLandPolygons, but in reverse.
 	 * @param hitMap A map of the 'hits' where the shore line intersects the boundary.
 	 */
-	private void createSeaPolygons(NavigableMap<Double, Way> hitMap) {
+	private List<Way> createSeaPolygons(NavigableMap<Double, Way> hitMap) {
 		NavigableSet<Double> hits = hitMap.navigableKeySet();
+		List<Way> areas = new ArrayList<>();
 		while (!hits.isEmpty()) {
 			Double hFirst = hits.last();
 			Double hStart = hFirst, hEnd;
@@ -1437,8 +1394,9 @@
 			} while (!finished);
 			w.addPoint(w.getFirstPoint()); // close shape
 			log.info("adding seaPoly, hits.size()", hits.size());
-			addAsSea(w);
+			areas.add(w);
 		}
+		return areas;
 	}
 
 	/**
@@ -1473,7 +1431,7 @@
 	 * map boundary.
 	 * @return A map of the 'hits' where the shore line intersects the boundary.
 	 */
-	private NavigableMap<Double, Way> findIntesectionPoints() {
+	private NavigableMap<Double, Way> findIntersectionPoints() {
 		NavigableMap<Double, Way> hitMap = new TreeMap<>();
 		for (Way w : shoreline) {
 			Coord pStart = w.getFirstPoint();
@@ -1504,11 +1462,9 @@
 				 * 4. Extend the ends of the shoreline to the nearest edge of the tile with ...,extend-sea-sectors
 				 *    This, in conjunction with close-gaps, normally works well but it isn't foolproof.
 				 */
-				List<Coord> points = w.getPoints();
 				if (allowSeaSectors) {
-					Way seaOrLand = new Way(w.getOriginalId());
+					Way seaOrLand = new Way(w.getOriginalId(), w.getPoints());
 					seaOrLand.markAsGeneratedFrom(w);
-					seaOrLand.getPoints().addAll(points);
 					int startLat = pStart.getHighPrecLat();
 					int startLon = pStart.getHighPrecLon();
 					int endLat = pEnd.getHighPrecLat();
@@ -1515,11 +1471,11 @@
 					int endLon = pEnd.getHighPrecLon();
 					boolean startLatIsCorner = (startLat > endLat) == (startLon > endLon);
 					int cornerLat, cornerLon;
-					if (generateSeaBackground) { // the tile is sea, with islands
+					if (generateSeaBackground) { // already have islands, so do this likewise
 						startLatIsCorner = !startLatIsCorner;
-						addAsLand(seaOrLand);
-					} else { // the tile is land, maybe with sea polygons on edge
-						addAsSea(seaOrLand);
+						islands.add(seaOrLand);
+					} else { // no islands, so more chance of sea being seen
+						antiIslands.add(seaOrLand);
 					}
 					if (startLatIsCorner) {
 						cornerLat = startLat;
@@ -1530,7 +1486,7 @@
 					}
 					seaOrLand.addPoint(Coord.makeHighPrecCoord(cornerLat, cornerLon));
 					seaOrLand.addPoint(pStart);
-					log.info("seaSector:", generateSeaBackground, startLatIsCorner, Way.clockwise(seaOrLand.getPoints()), seaOrLand.getBasicLogInformation());
+					log.info("seaSector:", islands.size(), antiIslands.size(), startLatIsCorner, Way.clockwise(seaOrLand.getPoints()), seaOrLand.getBasicLogInformation());
 				} else if (extendSeaSectors) {
 					// join to nearest tile border
 					if (null == hStart) {
@@ -1545,11 +1501,9 @@
 					hitMap.put(hStart, w);
 					hitMap.put(hEnd, null); // put this for verifyHits which then deletes it
 				} else {
-					// show the coastline even though we can't produce
-					// a polygon for the land
-					w.addTag("natural", "coastline");
-					log.error("adding sea shape that is not really closed");
-					saver.addWay(w);
+					// Can't produce a polygon for the land/sea.
+					// The original coastline might show, depending on the style
+					log.error("Unresolved section of coastline", w.getBasicLogInformation());
 				}
 			}
 		}
@@ -1563,7 +1517,7 @@
 	 * After checking, the end hit is removed
 	 */
 	private void verifyHits(NavigableMap<Double, Way> hitMap) {
-		log.debug("Islands", islands.size(), "Seas", antiIslands.size(), "hits", hitMap.size());
+		log.debug("Shorelines", shoreline.size(), "Islands", islands.size(), "Seas", antiIslands.size(), "hits", hitMap.size());
 		NavigableSet<Double> hits = hitMap.navigableKeySet();
 		Iterator<Double> iter = hits.iterator();
 		int lastStatus = 0, thisStatus;
@@ -1624,8 +1578,10 @@
 	/**
 	 * Calculate a Double that represents the position where the given point touches
 	 * the boundary.
-	 * 
-	 * @param bounds the boundary
+	 * Assumes that, if the way crosses the boundary, it has been cut so the end point
+	 * is exactly on the boundary.
+	 *
+	 * @param a the boundary
 	 * @param p the point
 	 * @return null if the point is not touching the boundary, else a value
 	 *         between 0.0 (inclusive) and 4.0 (exclusive), where 0 means the lower
@@ -1632,15 +1588,8 @@
 	 *         left corner, 0.5 means the middle of the bottom edge, 1.5 the
 	 *         middle of the right edge, 4 would be the lower left corner again
 	 */
-	private static Double getEdgeHit(Area bounds, Coord p) {
-		Double hit = getEdgeHit(bounds, p, 10); // 10 points in garmin units
-		if (hit != null && hit >= 4)
-			hit = 0.0;
-		return hit;
-	}
-
-	private static Double getEdgeHit(Area a, Coord p, int tolerance24) {
-		final int toleranceHp = tolerance24 << Coord.DELTA_SHIFT; 
+	private static Double getEdgeHit(Area a, Coord p) {
+		final int toleranceHp = 0; // tolerance24 << Coord.DELTA_SHIFT; 
 		final int latHp = p.getHighPrecLat();
 		final int lonHp = p.getHighPrecLon();
 		final int minLatHp = a.getMinLat() << Coord.DELTA_SHIFT;
@@ -1657,6 +1606,7 @@
 			return 2 + ((double) (maxLongHp - lonHp) / (maxLongHp - minLongHp));
 		} else if (lonHp <= minLongHp + toleranceHp) {
 			return 3 + ((double) (maxLatHp - latHp) / (maxLatHp - minLatHp));
+			// if exactly on bottom LHS corner (4), will have been caught by first case (0)
 		}
 		return null;
 	}
@@ -1665,12 +1615,12 @@
 	 * Find the nearest edge for supplied Coord p.
 	 */
 	private static Double getNextEdgeHit(Area a, Coord p) {
-		int latHp = p.getHighPrecLat();
-		int lonHp = p.getHighPrecLon();
-		int minLatHp = a.getMinLat() << Coord.DELTA_SHIFT;
-		int maxLatHp = a.getMaxLat() << Coord.DELTA_SHIFT;
-		int minLongHp = a.getMinLong() << Coord.DELTA_SHIFT;
-		int maxLongHp = a.getMaxLong() << Coord.DELTA_SHIFT;
+		final int latHp = p.getHighPrecLat();
+		final int lonHp = p.getHighPrecLon();
+		final int minLatHp = a.getMinLat() << Coord.DELTA_SHIFT;
+		final int maxLatHp = a.getMaxLat() << Coord.DELTA_SHIFT;
+		final int minLongHp = a.getMinLong() << Coord.DELTA_SHIFT;
+		final int maxLongHp = a.getMaxLong() << Coord.DELTA_SHIFT;
 
 		log.info(String.format("getNextEdgeHit: (%d %d) (%d %d %d %d)", latHp, lonHp, minLatHp, minLongHp, maxLatHp, maxLongHp));
 		// shortest distance to border (init with distance to southern border)
