/* 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.bcr; import slash.navigation.base.IniFileFormat; import slash.navigation.base.ParserContext; import slash.navigation.base.RouteCharacteristics; import slash.navigation.common.NavigationPosition; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import static slash.common.io.Transfer.*; import static slash.navigation.bcr.BcrPosition.NO_ALTITUDE_DEFINED; /** * The base of all Map & Guide Tourenplaner Route formats. * * @author Christian Pesch */ public abstract class BcrFormat extends IniFileFormat<BcrRoute> { private static final Logger log = Logger.getLogger(BcrFormat.class.getName()); private static final Preferences preferences = Preferences.userNodeForPackage(BcrFormat.class); static final String CLIENT_TITLE = "CLIENT"; static final String COORDINATES_TITLE = "COORDINATES"; static final String DESCRIPTION_TITLE = "DESCRIPTION"; static final String ROUTE_TITLE = "ROUTE"; private static final Pattern SECTION_TITLE_PATTERN = Pattern. compile("\\" + SECTION_PREFIX + "([\\p{Upper}|\\s]+)" + SECTION_POSTFIX); static final char VALUE_SEPARATOR = ','; private static final Pattern COORDINATES_VALUE_PATTERN = Pattern.compile("(-?\\d+)" + VALUE_SEPARATOR + "(-?\\d+)"); private static final Pattern ALTITUDE_VALUE_PATTERN = Pattern.compile("([\\w|\\s]+)" + VALUE_SEPARATOR + "([^,]+),?.*"); static final String ROUTE_NAME = "ROUTENAME"; static final String EXPECTED_DISTANCE = "EXP_DISTANCE"; static final String DESCRIPTION_LINE_COUNT = "DESCRIPTIONLINES"; static final String DESCRIPTION = "DESCRIPTION"; static final String CREATOR = "CREATOR"; public String getExtension() { return ".bcr"; } public int getMaximumPositionCount() { return preferences.getInt("maximumPositionCount", 1 + 99 + 1); } public boolean isSupportsMultipleRoutes() { return false; } public boolean isWritingRouteCharacteristics() { return false; } @SuppressWarnings("unchecked") public <P extends NavigationPosition> BcrRoute createRoute(RouteCharacteristics characteristics, String name, List<P> positions) { return new BcrRoute(this, name, null, (List<BcrPosition>) positions); } public void read(BufferedReader reader, String encoding, ParserContext<BcrRoute> context) throws IOException { List<BcrSection> sections = new ArrayList<>(); List<BcrPosition> positions = new ArrayList<>(); BcrSection current = null; while (true) { String line = reader.readLine(); if (line == null) break; if (line.length() == 0) continue; if (isSectionTitle(line)) { BcrSection section = new BcrSection(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)) { extractPositions(sections, positions); if (positions.size() >= 2) context.appendRoute(new BcrRoute(this, sections, positions)); } } 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 BcrSection findSection(List<BcrSection> sections, String title) { for (BcrSection section : sections) { if (title.equals(section.getTitle())) return section; } return null; } private boolean existsSection(List<BcrSection> sections, String title) { return findSection(sections, title) != null; } protected abstract boolean isValidDescription(BcrSection description); private boolean hasValidSections(List<BcrSection> sections) { if (existsSection(sections, CLIENT_TITLE) && existsSection(sections, DESCRIPTION_TITLE) && existsSection(sections, COORDINATES_TITLE)) { BcrSection client = findSection(sections, CLIENT_TITLE); BcrSection coordinates = findSection(sections, COORDINATES_TITLE); BcrSection description = findSection(sections, DESCRIPTION_TITLE); return isValidDescription(description) && client != null && coordinates != null && description != null && client.getStationCount() == coordinates.getStationCount() && coordinates.getStationCount() == description.getStationCount(); } return false; } private void extractPositions(List<BcrSection> sections, List<BcrPosition> positions) { BcrSection client = findSection(sections, CLIENT_TITLE); BcrSection coordinates = findSection(sections, COORDINATES_TITLE); BcrSection description = findSection(sections, DESCRIPTION_TITLE); if(client == null || coordinates == null || description == null) return; for (int i = 1; i < client.getStationCount(); i++) { String clientStr = client.getStation(i); String coordinatesStr = coordinates.getStation(i); String descriptionStr = description.getStation(i); positions.add(parsePosition(clientStr, coordinatesStr, descriptionStr)); } client.removeStations(); coordinates.removeStations(); description.removeStations(); } BcrPosition parsePosition(String client, String coordinate, String description) { Matcher coordinateMatcher = COORDINATES_VALUE_PATTERN.matcher(coordinate); if (!coordinateMatcher.matches()) throw new IllegalArgumentException("'" + coordinate + "' does not match coordinates pattern"); String x = coordinateMatcher.group(1); String y = coordinateMatcher.group(2); long altitude = NO_ALTITUDE_DEFINED; Matcher clientMatcher = ALTITUDE_VALUE_PATTERN.matcher(client); if (!clientMatcher.matches()) log.info("'" + client + "' does not match client station pattern; ignoring it"); else { String string = clientMatcher.group(2); try { Long aLong = parseLong(string); if (aLong != null) altitude = aLong; } catch (NumberFormatException e) { log.info("'" + string + "' is not a valid altitude; ignoring it"); } } return new BcrPosition(parseInt(x), parseInt(y), altitude, trim(description)); } protected abstract void writePosition(BcrPosition position, PrintWriter writer, int index); public void write(BcrRoute route, PrintWriter writer, int startIndex, int endIndex) { List<BcrPosition> positions = route.getPositions(); for (BcrSection section : route.getSections()) { writer.println(SECTION_PREFIX + section.getTitle() + SECTION_POSTFIX); for (String name : section.keySet()) { if (!name.equals(CREATOR) && !name.equals(ROUTE_NAME) && !name.equals(EXPECTED_DISTANCE)) { String value = section.get(name); if (value == null) value = ""; writer.println(name + NAME_VALUE_SEPARATOR + value); } } if (CLIENT_TITLE.equals(section.getTitle())) { writer.println(CREATOR + NAME_VALUE_SEPARATOR + GENERATED_BY); writer.println(ROUTE_NAME + NAME_VALUE_SEPARATOR + asRouteName(route.getName())); double length = route.getDistance(); if(length > 0) length = length / 1000.0; writer.println(EXPECTED_DISTANCE + NAME_VALUE_SEPARATOR + (int)length); int index = 1; int maxIndex = positions.size(); for (int i = startIndex; i < endIndex; i++) { BcrPosition position = positions.get(i); long altitude = position.getAltitude(); boolean center = position.getStreet() != null && position.getStreet().equals(BcrPosition.STREET_DEFINES_CENTER_NAME); boolean first = index == 1; boolean last = index == maxIndex; String altitutdeDescription = center || first || last ? "TOWN" : "Standort"; writer.println(BcrSection.STATION_PREFIX + (index++) + NAME_VALUE_SEPARATOR + altitutdeDescription + VALUE_SEPARATOR + altitude); } } if (COORDINATES_TITLE.equals(section.getTitle())) { int index = 1; for (int i = startIndex; i < endIndex; i++) { BcrPosition position = positions.get(i); writer.println(BcrSection.STATION_PREFIX + (index++) + NAME_VALUE_SEPARATOR + position.getX() + VALUE_SEPARATOR + position.getY()); } } if (DESCRIPTION_TITLE.equals(section.getTitle())) { int index = 1; for (int i = startIndex; i < endIndex; i++) { BcrPosition position = positions.get(i); writePosition(position, writer, index++); } } } } }