/*
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.hgt;
import slash.navigation.common.BoundingBox;
import slash.navigation.common.LongitudeAndLatitude;
import slash.navigation.datasources.DataSource;
import slash.navigation.datasources.Downloadable;
import slash.navigation.datasources.Fragment;
import slash.navigation.download.Action;
import slash.navigation.download.Download;
import slash.navigation.download.DownloadManager;
import slash.navigation.download.FileAndChecksum;
import slash.navigation.elevation.ElevationService;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
import java.util.prefs.Preferences;
import static java.lang.String.format;
import static slash.common.io.Directories.ensureDirectory;
import static slash.common.io.Directories.getApplicationDirectory;
import static slash.common.io.Files.removeExtension;
/**
* Encapsulates access to HGT files.
*
* @author Robert "robekas", Christian Pesch
*/
public class HgtFiles implements ElevationService {
private static final Preferences preferences = Preferences.userNodeForPackage(HgtFiles.class);
private static final String DIRECTORY_PREFERENCE = "directory";
private static final String BASE_URL_PREFERENCE = "baseUrl";
private static final String DOT_HGT = ".hgt";
private final Map<java.io.File, RandomAccessFile> randomAccessFileCache = new HashMap<>();
private final DataSource dataSource;
private final DownloadManager downloadManager;
public HgtFiles(DataSource dataSource, DownloadManager downloadManager) {
this.dataSource = dataSource;
this.downloadManager = downloadManager;
}
public String getName() {
return dataSource.getName();
}
String getBaseUrl() {
return preferences.get(BASE_URL_PREFERENCE + getName(), dataSource.getBaseUrl());
}
public boolean isDownload() {
return true;
}
public boolean isOverQueryLimit() {
return false;
}
public boolean isSupportsPath() {
return true;
}
public String getPath() {
return preferences.get(DIRECTORY_PREFERENCE + getName(), "");
}
public void setPath(String path) {
preferences.put(DIRECTORY_PREFERENCE + getName(), path);
}
private java.io.File getDirectory() {
String directoryName = getPath();
java.io.File f = new java.io.File(directoryName);
if (!f.exists())
directoryName = getApplicationDirectory(dataSource.getDirectory()).getAbsolutePath();
return ensureDirectory(directoryName);
}
String createFileKey(double longitude, double latitude) {
int longitudeAsInteger = (int) longitude;
int latitudeAsInteger = (int) latitude;
return format("%s%02d%s%03d" + DOT_HGT, (latitude < 0) ? "S" : "N",
(latitude < 0) ? ((latitudeAsInteger - 1) * -1) : latitudeAsInteger,
(longitude < 0) ? "W" : "E",
(longitude < 0) ? ((longitudeAsInteger - 1) * -1) : longitudeAsInteger);
}
private java.io.File createFile(String key) {
return new java.io.File(getDirectory(), key);
}
public Double getElevationFor(double longitude, double latitude) throws IOException {
java.io.File file = createFile(createFileKey(longitude, latitude));
if (!file.exists())
return null;
RandomAccessFile randomAccessFile = randomAccessFileCache.get(file);
if (randomAccessFile == null) {
randomAccessFile = new RandomAccessFile(file, "r");
randomAccessFileCache.put(file, randomAccessFile);
}
return new ElevationTile(randomAccessFile).getElevationFor(longitude, latitude);
}
public void dispose() {
for (RandomAccessFile randomAccessFile : randomAccessFileCache.values())
try {
randomAccessFile.close();
} catch (IOException e) {
throw new IllegalArgumentException("Cannot close random access file" + randomAccessFile);
}
randomAccessFileCache.clear();
}
public void downloadElevationDataFor(List<LongitudeAndLatitude> longitudeAndLatitudes, boolean waitForDownload) {
Set<String> keys = new HashSet<>();
for (LongitudeAndLatitude longitudeAndLatitude : longitudeAndLatitudes) {
keys.add(createFileKey(longitudeAndLatitude.longitude, longitudeAndLatitude.latitude));
}
Collection<Downloadable> downloadables = new HashSet<>();
for (String key : keys) {
Fragment<Downloadable> fragment = dataSource.getFragment(key);
// fallback as long as .hgt is not part of the keys
if (fragment == null)
fragment = dataSource.getFragment(removeExtension(key));
if (fragment != null && !createFile(fragment.getKey()).exists())
downloadables.add(fragment.getDownloadable());
}
Collection<Download> downloads = new HashSet<>();
for (Downloadable downloadable : downloadables) {
downloads.add(download(downloadable));
}
if (!downloads.isEmpty() && waitForDownload)
downloadManager.waitForCompletion(downloads);
}
private Download download(Downloadable downloadable) {
List<FileAndChecksum> fragments = new ArrayList<>();
for (Fragment otherFragments : downloadable.getFragments()) {
String key = otherFragments.getKey();
// ignore fragment keys without extension which are reported by old RouteConverter releases
if (key.endsWith(DOT_HGT))
fragments.add(new FileAndChecksum(createFile(key), otherFragments.getLatestChecksum()));
}
String uri = downloadable.getUri();
String url = getBaseUrl() + uri;
return downloadManager.queueForDownload(getName() + " Elevation Tile: " + uri, url, Action.valueOf(dataSource.getAction()),
new FileAndChecksum(getDirectory(), downloadable.getLatestChecksum()), fragments);
}
private Collection<Fragment<Downloadable>> getDownloadablesFor(BoundingBox boundingBox) {
Collection<Fragment<Downloadable>> result = new HashSet<>();
double longitude = boundingBox.getSouthWest().getLongitude();
while (longitude < boundingBox.getNorthEast().getLongitude()) {
double latitude = boundingBox.getSouthWest().getLatitude();
while (latitude < boundingBox.getNorthEast().getLatitude()) {
String key = createFileKey(longitude, latitude);
Fragment<Downloadable> fragment = dataSource.getFragment(key);
if (fragment != null)
result.add(fragment);
latitude += 1.0;
}
longitude += 1.0;
}
return result;
}
private Collection<Fragment<Downloadable>> getDownloadablesFor(List<BoundingBox> boundingBoxes) {
Collection<Fragment<Downloadable>> result = new HashSet<>();
for (BoundingBox boundingBox : boundingBoxes)
result.addAll(getDownloadablesFor(boundingBox));
return result;
}
private Collection<Downloadable> asDownloadableSet(Collection<Fragment<Downloadable>> fragments) {
Collection<Downloadable> result = new ArrayList<>();
for (Fragment<Downloadable> fragment : fragments)
result.add(fragment.getDownloadable());
return result;
}
public long calculateRemainingDownloadSize(List<BoundingBox> boundingBoxes) {
Collection<Fragment<Downloadable>> fragments = getDownloadablesFor(boundingBoxes);
Collection<Downloadable> downloadables = new HashSet<>();
for (Fragment<Downloadable> fragment : fragments) {
java.io.File file = createFile(fragment.getKey());
if (!file.exists())
downloadables.add(fragment.getDownloadable());
}
long notExists = 0L;
for (Downloadable downloadable : downloadables) {
Long contentLength = downloadable.getLatestChecksum().getContentLength();
if (contentLength == null)
continue;
notExists += contentLength;
}
return notExists;
}
public void downloadElevationData(List<BoundingBox> boundingBoxes) {
Collection<Fragment<Downloadable>> fragments = getDownloadablesFor(boundingBoxes);
for (Downloadable downloadable : asDownloadableSet(fragments)) {
download(downloadable);
}
}
}