/******************************************************************************* * Copyright (c) MOBAC developers * * This program 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.mapsources.custom; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.swing.JOptionPane; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import mobac.exceptions.TileException; import mobac.gui.mapview.PreviewMap; import mobac.mapsources.MapSourceTools; import mobac.mapsources.mapspace.MapSpaceFactory; import mobac.program.interfaces.FileBasedMapSource; import mobac.program.interfaces.MapSpace; import mobac.program.interfaces.MapSpace.MapSpaceType; import mobac.program.jaxb.ColorAdapter; import mobac.program.model.MapSourceLoaderInfo; import mobac.program.model.TileImageType; import mobac.utilities.I18nUtils; import mobac.utilities.Utilities; import org.apache.log4j.Logger; @XmlRootElement(name = "localTileFiles") public class CustomLocalTileFilesMapSource implements FileBasedMapSource { private static final Logger log = Logger.getLogger(CustomLocalTileFilesMapSource.class); private MapSourceLoaderInfo loaderInfo = null; //private MapSpace mapSpace = MapSpaceFactory.getInstance(256, MapSpaceType.msMercatorSpherical); private boolean initialized = false; private String fileSyntax = null; private TileImageType tileImageType = null; @XmlElement(nillable = false, defaultValue = "CustomLocal") private String name = "Custom"; private int minZoom = PreviewMap.MIN_ZOOM; private int maxZoom = PreviewMap.MAX_ZOOM; @XmlElement(required = true) private File sourceFolder = null; @XmlElement() private CustomMapSourceType sourceType = CustomMapSourceType.DIR_ZOOM_X_Y; @XmlElement(defaultValue = "false") private boolean invertYCoordinate = false; @XmlElement(defaultValue = "#000000") @XmlJavaTypeAdapter(ColorAdapter.class) private Color backgroundColor = Color.BLACK; @XmlElement(defaultValue = "") private String emptyTileFile = ""; @XmlElement(defaultValue = "msMercatorSpherical") private MapSpaceType mapSpaceType = MapSpaceType.msMercatorSpherical; @XmlElement(defaultValue = "false") private boolean hiddenDefault = false; public CustomLocalTileFilesMapSource() { super(); } public synchronized void initialize() { if (initialized) return; reinitialize(); } public void reinitialize() { try { if (!sourceFolder.isDirectory()) { JOptionPane.showMessageDialog(null, String.format(I18nUtils.localizedStringForKey("msg_environment_invalid_source_folder"), name, sourceFolder.toString()), I18nUtils.localizedStringForKey("msg_environment_invalid_source_folder_title"), JOptionPane.ERROR_MESSAGE); initialized = true; return; } switch (sourceType) { case DIR_ZOOM_X_Y: case DIR_ZOOM_Y_X: initializeDirType(); break; case QUADKEY: initializeQuadKeyType(); break; default: throw new RuntimeException("Invalid source type"); } } finally { initialized = true; } } private void initializeDirType() { /* Update zoom levels */ FileFilter ff = new NumericDirFileFilter(); File[] zoomDirs = sourceFolder.listFiles(ff); if (zoomDirs.length < 1) { JOptionPane.showMessageDialog(null, String.format(I18nUtils.localizedStringForKey("msg_environment_invalid_source_folder_zoom"), name ,sourceFolder), I18nUtils.localizedStringForKey("msg_environment_invalid_source_folder_title"), JOptionPane.ERROR_MESSAGE); initialized = true; return; } int min = PreviewMap.MAX_ZOOM; int max = PreviewMap.MIN_ZOOM; for (File file : zoomDirs) { int z = Integer.parseInt(file.getName()); min = Math.min(min, z); max = Math.max(max, z); } minZoom = min; maxZoom = max; for (File zDir : zoomDirs) { for (File xDir : zDir.listFiles(ff)) { try { xDir.listFiles(new FilenameFilter() { String syntax = "%d/%d/%d"; public boolean accept(File dir, String name) { String[] parts = name.split("\\."); if (parts.length < 2 || parts.length > 3) return false; syntax += "." + parts[1]; if (parts.length == 3) syntax += "." + parts[2]; tileImageType = TileImageType.getTileImageType(parts[1]); fileSyntax = syntax; log.debug("Detected file syntax: " + fileSyntax + " tileImageType=" + tileImageType); throw new RuntimeException("break"); } }); } catch (RuntimeException e) { } catch (Exception e) { log.error(e.getMessage()); } return; } } } private void initializeQuadKeyType() { String[] files = sourceFolder.list(); Pattern p = Pattern.compile("([0123]+)\\.(png|gif|jpg)", Pattern.CASE_INSENSITIVE); String fileExt = null; for (String file : files) { Matcher m = p.matcher(file); if (!m.matches()) continue; fileExt = m.group(2); break; } if (fileExt == null) return; // Error no suitable file found fileSyntax = "%s." + fileExt; tileImageType = TileImageType.getTileImageType(fileExt); p = Pattern.compile("([0123]+)\\.(" + fileExt + ")", Pattern.CASE_INSENSITIVE); int min = PreviewMap.MAX_ZOOM; int max = 1; for (String file : files) { Matcher m = p.matcher(file); if (!m.matches()) continue; if (fileSyntax == null) fileSyntax = "%s." + m.group(2); int z = m.group(1).length(); min = Math.min(min, z); max = Math.max(max, z); } minZoom = min; maxZoom = max; } public byte[] getTileData(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException, InterruptedException { if (!initialized) initialize(); if (fileSyntax == null) return null; if (log.isTraceEnabled()) log.trace(String.format("Loading tile z=%d x=%d y=%d", zoom, x, y)); if (invertYCoordinate) y = ((1 << zoom) - y - 1); String fileName; switch (sourceType) { case DIR_ZOOM_X_Y: fileName = String.format(fileSyntax, zoom, x, y); break; case DIR_ZOOM_Y_X: fileName = String.format(fileSyntax, zoom, y, x); break; case QUADKEY: fileName = String.format(fileSyntax, MapSourceTools.encodeQuadTree(zoom, x, y)); break; default: throw new RuntimeException("Invalid source type"); } File file = new File(sourceFolder, fileName); if (!file.exists() && !emptyTileFile.equals("")) file = new File(emptyTileFile); try { return Utilities.getFileBytes(file); } catch (FileNotFoundException e) { log.debug("Map tile file not found: " + file.getAbsolutePath()); return null; } } public BufferedImage getTileImage(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException, InterruptedException { byte[] data = getTileData(zoom, x, y, loadMethod); if (data == null) return null; return ImageIO.read(new ByteArrayInputStream(data)); } public TileImageType getTileImageType() { return tileImageType; } public int getMaxZoom() { return maxZoom; } public int getMinZoom() { return minZoom; } public String getName() { return name; } @Override public String toString() { return name; } public MapSpace getMapSpace() { //return mapSpace; return MapSpaceFactory.getInstance(256, mapSpaceType); } public Color getBackgroundColor() { return backgroundColor; } public String getEmptyTileFile() { return emptyTileFile; } @XmlTransient public MapSourceLoaderInfo getLoaderInfo() { return loaderInfo; } public void setLoaderInfo(MapSourceLoaderInfo loaderInfo) { this.loaderInfo = loaderInfo; } private static class NumericDirFileFilter implements FileFilter { private Pattern p = Pattern.compile("^\\d+$"); public boolean accept(File f) { if (!f.isDirectory()) return false; return p.matcher(f.getName()).matches(); } } public MapSpaceType getMapSpaceType() { return mapSpaceType; } @Override public boolean getHiddenDefault() { return hiddenDefault; } }