/* This file is part of RouteConverter. RouteConverter is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. RouteConverter 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. You should have received a copy of the GNU General Public License along with RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.ovl; import slash.navigation.base.*; import slash.navigation.common.BoundingBox; import slash.navigation.common.NavigationPosition; import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.util.Collections.singletonList; import static slash.common.io.Transfer.ISO_LATIN1_ENCODING; import static slash.navigation.base.RouteCharacteristics.Route; import static slash.navigation.base.RouteCharacteristics.Track; import static slash.navigation.common.NavigationConversion.formatPositionAsString; import static slash.navigation.ovl.OvlSection.GROUP; import static slash.navigation.ovl.OvlSection.TEXT; /** * Reads and writes Top50 OVL ASCII (.ovl) files. * * @author Christian Pesch */ public class OvlFormat extends IniFileFormat<OvlRoute> implements MultipleRoutesFormat<OvlRoute> { static final String SYMBOL_TITLE = "Symbol"; static final String OVERLAY_TITLE = "Overlay"; static final String MAPLAGE_TITLE = "MapLage"; private static final String SYMBOL_COUNT = "Symbols"; // static final String ROUTE_NAME = "RouteName"; private static final String CREATOR = "Creator"; private static final Pattern SECTION_TITLE_PATTERN = Pattern. compile("\\" + SECTION_PREFIX + "(" + SYMBOL_TITLE + " \\d+|" + OVERLAY_TITLE + "|" + MAPLAGE_TITLE + ")\\" + SECTION_POSTFIX); public String getExtension() { return ".ovl"; } public String getName() { return "Top50 OVL ASCII (*" + getExtension() + ")"; } public int getMaximumPositionCount() { return UNLIMITED_MAXIMUM_POSITION_COUNT; } public boolean isSupportsMultipleRoutes() { return true; } public boolean isWritingRouteCharacteristics() { return false; } @SuppressWarnings({"unchecked"}) public <P extends NavigationPosition> OvlRoute createRoute(RouteCharacteristics characteristics, String name, List<P> positions) { return new OvlRoute(characteristics, name, (List<Wgs84Position>) positions); } public void read(BufferedReader reader, String encoding, ParserContext<OvlRoute> context) throws IOException { List<OvlSection> sections = new ArrayList<>(); OvlSection current = null; while (true) { String line = reader.readLine(); if (line == null) break; if (line.length() == 0) continue; if (isSectionTitle(line)) { OvlSection section = new OvlSection(parseSectionTitle(line)); sections.add(section); current = section; } if (isNameValue(line)) { if (current == null) { // name value without section means this isn't the file format we expect return; } else current.put(parseName(line), parseValue(line)); } } if (hasValidSections(sections)) context.appendRoutes(extractRoutes(sections)); } boolean isSectionTitle(String line) { Matcher matcher = SECTION_TITLE_PATTERN.matcher(line); return matcher.matches(); } String parseSectionTitle(String line) { Matcher matcher = SECTION_TITLE_PATTERN.matcher(line); if (!matcher.matches()) throw new IllegalArgumentException("'" + line + "' does not match"); return matcher.group(1); } private OvlSection findSection(List<OvlSection> sections, String title) { for (OvlSection section : sections) { if (title.equals(section.getTitle())) return section; } return null; } private boolean existsSection(List<OvlSection> sections, String title) { return findSection(sections, title) != null; } private boolean hasValidSections(List<OvlSection> sections) { if (!existsSection(sections, OVERLAY_TITLE)) return false; OvlSection overlay = findSection(sections, OVERLAY_TITLE); int symbolCount = getSymbolCount(overlay); if (symbolCount == 0) return false; int sectionCount = 0; for (int i = 0; i < symbolCount; i++) { if (existsSection(sections, SYMBOL_TITLE + " " + (i + 1))) sectionCount++; } return sectionCount > 0; } private int getSymbolCount(OvlSection overlay) { String symbols = overlay.get(SYMBOL_COUNT); return symbols != null ? Integer.parseInt(symbols) : 0; } private List<OvlRoute> extractRoutes(List<OvlSection> sections) { List<OvlRoute> result = new ArrayList<>(); OvlSection mapLage = findSection(sections, MAPLAGE_TITLE); if(mapLage == null) mapLage = new OvlSection(MAPLAGE_TITLE); OvlSection overlay = findSection(sections, OVERLAY_TITLE); int symbolCount = getSymbolCount(overlay); // process all sections with same group into one route Map<Integer, List<OvlSection>> sectionsByGroup = new LinkedHashMap<>(); List<OvlSection> sectionsWithoutGroup = new ArrayList<>(); for (int i = 0; i < symbolCount; i++) { OvlSection section = findSection(sections, SYMBOL_TITLE + " " + (i + 1)); if (section != null) { Integer group = section.getGroup(); if (group != null) { List<OvlSection> groupedSections = sectionsByGroup.get(group); if (groupedSections == null) { groupedSections = new ArrayList<>(); sectionsByGroup.put(group, groupedSections); } groupedSections.add(section); } else sectionsWithoutGroup.add(section); } } for (OvlSection section : sectionsWithoutGroup) { OvlRoute route = extractRoute(section, overlay, mapLage); result.add(route); } for (List<OvlSection> sectionList : sectionsByGroup.values()) { OvlRoute route = extractRoute(sectionList, overlay, mapLage); result.add(route); } return result; } private RouteCharacteristics estimateCharacteristics(int positionCount) { return positionCount > 100 ? Track : Route; } private OvlRoute extractRoute(OvlSection symbol, OvlSection overlay, OvlSection mapLage) { List<Wgs84Position> positions = new ArrayList<>(); int positionCount = symbol.getPositionCount(); for (int i = 0; i < positionCount; i++) { positions.add(symbol.getPosition(i)); } symbol.removePositions(); return new OvlRoute(this, estimateCharacteristics(positionCount), symbol.getTitle(), symbol, overlay, mapLage, positions); } private OvlRoute extractRoute(List<OvlSection> symbols, OvlSection overlay, OvlSection mapLage) { List<Wgs84Position> positions = new ArrayList<>(); for (OvlSection symbol : symbols) { int positionCount = symbol.getPositionCount(); for (int i = 0; i < positionCount; i++) { positions.add(symbol.getPosition(i)); } symbol.removePositions(); } OvlSection symbol = symbols.size() > 0 ? symbols.get(0) : null; return new OvlRoute(this, estimateCharacteristics(positions.size()), mapLage.getTitle(), symbol, overlay, mapLage, positions); } private void writeSection(OvlSection section, PrintWriter writer, Collection<String> ignoreNames) { for (String name : section.keySet()) { if (!ignoreNames.contains(name)) { String value = section.get(name); if (value == null) value = ""; writer.println(name + NAME_VALUE_SEPARATOR + value); } } } private void writeMissingAttribute(OvlSection section, PrintWriter writer, String name, String defaultValue) { if (section.get(name) == null) writer.println(name + NAME_VALUE_SEPARATOR + defaultValue); } private void writeSymbol(OvlRoute route, PrintWriter writer, int startIndex, int endIndex, int symbolIndex) { writer.println(SECTION_PREFIX + SYMBOL_TITLE + " " + symbolIndex + SECTION_POSTFIX); writer.println(GROUP + NAME_VALUE_SEPARATOR + symbolIndex); writeSection(route.getSymbol(), writer, singletonList(TEXT)); writeMissingAttribute(route.getSymbol(), writer, "Typ", "3"); writeMissingAttribute(route.getSymbol(), writer, "Col", "3"); writeMissingAttribute(route.getSymbol(), writer, "Zoom", "1"); writeMissingAttribute(route.getSymbol(), writer, "Size", "106"); writeMissingAttribute(route.getSymbol(), writer, "Art", "1"); writer.println(OvlSection.POSITION_COUNT + NAME_VALUE_SEPARATOR + (endIndex - startIndex)); List<Wgs84Position> positions = route.getPositions(); int index = 0; for (int i = startIndex; i < endIndex; i++) { Wgs84Position position = positions.get(i); writer.println(OvlSection.X_POSITION + index + NAME_VALUE_SEPARATOR + position.getLongitude()); writer.println(OvlSection.Y_POSITION + index + NAME_VALUE_SEPARATOR + position.getLatitude()); index++; } writer.println(TEXT + NAME_VALUE_SEPARATOR + asRouteName(route.getName())); } private void writeOverlay(OvlRoute route, PrintWriter writer, int symbolCount) { writer.println(SECTION_PREFIX + OVERLAY_TITLE + SECTION_POSTFIX); writeSection(route.getOverlay(), writer, singletonList(SYMBOL_COUNT)); writer.println(SYMBOL_COUNT + NAME_VALUE_SEPARATOR + symbolCount); } private void writeMapLage(OvlRoute route, PrintWriter writer) { writer.println(SECTION_PREFIX + MAPLAGE_TITLE + SECTION_POSTFIX); writeSection(route.getMapLage(), writer, singletonList(CREATOR)); // Top. Karte 1:50.000 Hessen // Top. Karte 1:50.000 Nieders. // Top. Karte 1:50000 Sh/HH writeMissingAttribute(route.getMapLage(), writer, "MapName", "Bundesrepublik 1:1 Mio"); writeMissingAttribute(route.getMapLage(), writer, "DimmFc", "100"); writeMissingAttribute(route.getMapLage(), writer, "ZoomFc", "100"); NavigationPosition center = new BoundingBox(route.getPositions()).getCenter(); writeMissingAttribute(route.getMapLage(), writer, "CenterLat", formatPositionAsString(center.getLatitude())); writeMissingAttribute(route.getMapLage(), writer, "CenterLong", formatPositionAsString(center.getLongitude())); writer.println(CREATOR + NAME_VALUE_SEPARATOR + GENERATED_BY); } public void write(OvlRoute route, PrintWriter writer, int startIndex, int endIndex) { writeSymbol(route, writer, startIndex, endIndex, 1); writeOverlay(route, writer, 1); writeMapLage(route, writer); } public void write(List<OvlRoute> routes, OutputStream target) throws IOException { PrintWriter writer = new PrintWriter(new OutputStreamWriter(target, ISO_LATIN1_ENCODING)); try { int symbols = 0; for (OvlRoute route : routes) { writeSymbol(route, writer, 0, route.getPositionCount(), ++symbols); } // stupid logic: the first route written determines the properties of the Overlays and MapLage sections OvlRoute first = routes.size() > 0 ? routes.get(0) : null; if (first != null) { writeOverlay(first, writer, symbols); writeMapLage(first, writer); } } finally { writer.flush(); writer.close(); } } }