/******************************************************************************* * 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.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; 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.Logging; 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 = "localTileZip") public class CustomLocalTileZipMapSource implements FileBasedMapSource { private static final Logger log = Logger.getLogger(CustomLocalTileZipMapSource.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(name = "zipFile", required = true) private File[] zipFiles = new File[] {}; @XmlElement() private CustomMapSourceType sourceType = CustomMapSourceType.DIR_ZOOM_X_Y; @XmlElement(defaultValue = "false") private boolean invertYCoordinate = false; private LinkedList<ZipFile> zips = new LinkedList<ZipFile>(); @XmlElement(defaultValue = "#000000") @XmlJavaTypeAdapter(ColorAdapter.class) private Color backgroundColor = Color.BLACK; @XmlElement(defaultValue = "false") private boolean hiddenDefault = false; public CustomLocalTileZipMapSource() { super(); } protected synchronized void openZipFile() { for (File zipFile : zipFiles) { if (!zipFile.isFile()) { JOptionPane.showMessageDialog(null, String.format(I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_zip_title"), name, zipFile.toString()), I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_zip_title"), JOptionPane.ERROR_MESSAGE); } else { try { Logging.LOG.debug("Opening zip file " + zipFile.getAbsolutePath()); zips.add(new ZipFile(zipFile)); Logging.LOG.debug("Zip file open completed"); } catch (Exception e) { JOptionPane.showMessageDialog(null, String.format(I18nUtils.localizedStringForKey("msg_custom_map_failed_open_source_zip"), name, zipFile.toString()), I18nUtils.localizedStringForKey("msg_custom_map_failed_open_source_zip_title"), JOptionPane.ERROR_MESSAGE); } } } } public synchronized void initialize() { if (initialized) return; reinitialize(); } public void reinitialize() { try { openZipFile(); if (zips.size() == 0) 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; } } public synchronized void initializeDirType() { int min = PreviewMap.MAX_ZOOM; int max = PreviewMap.MIN_ZOOM; for (ZipFile zip : zips) { for (int z = PreviewMap.MAX_ZOOM; z > PreviewMap.MIN_ZOOM; z--) { ZipEntry entry = zip.getEntry(Integer.toString(z) + "/"); if (entry != null) { max = Math.max(max, z); break; } } for (int z = PreviewMap.MIN_ZOOM; z < PreviewMap.MAX_ZOOM; z++) { ZipEntry entry = zip.getEntry(Integer.toString(z) + "/"); if (entry != null) { min = Math.min(min, z); break; } } } minZoom = min; maxZoom = max; Enumeration<? extends ZipEntry> entries = zips.get(0).entries(); String syntax = "%d/%d/%d"; while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry.isDirectory()) continue; String name = entry.getName(); int i = name.lastIndexOf("/"); name = name.substring(i + 1); String[] parts = name.split("\\."); if (parts.length < 2 || parts.length > 3) break; syntax += "." + parts[1]; tileImageType = TileImageType.getTileImageType(parts[1]); if (parts.length == 3) syntax += "." + parts[2]; fileSyntax = syntax; log.debug("Detected file syntax: " + fileSyntax + " tileImageType=" + tileImageType); break; } } public synchronized void initializeQuadKeyType() { Pattern p = Pattern.compile("([0123]+)\\.(png|gif|jpg)", Pattern.CASE_INSENSITIVE); Enumeration<? extends ZipEntry> entries = zips.get(0).entries(); String fileExt = null; while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); Matcher m = p.matcher(entry.getName()); 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 (ZipFile zipFile : zips) { entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); Matcher m = p.matcher(entry.getName()); 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); ZipEntry entry = null; 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"); } for (ZipFile zip : zips) { entry = zip.getEntry(fileName); if (entry != null) { InputStream in = zip.getInputStream(entry); byte[] data = Utilities.getInputBytes(in); in.close(); return data; } } log.debug("Map tile file not found in zip files: " + fileName); 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; } public Color getBackgroundColor() { return backgroundColor; } @XmlTransient public MapSourceLoaderInfo getLoaderInfo() { return loaderInfo; } public void setLoaderInfo(MapSourceLoaderInfo loaderInfo) { this.loaderInfo = loaderInfo; } @Override public boolean getHiddenDefault() { return hiddenDefault; } }