// Returns the task if we started the task or the task is already started. private Future<?> startTaskIfNeeded(int index, int which) { if (index < mActiveStart || index >= mActiveEnd) return null; ImageEntry entry = mImageCache.get(getVersion(index)); if (entry == null) return null; if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null) { return entry.screenNailTask; } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) { return entry.fullImageTask; } MediaItem item = mData[index % DATA_CACHE_SIZE]; Utils.assertTrue(item != null); if (which == BIT_SCREEN_NAIL && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) { entry.requestedBits |= BIT_SCREEN_NAIL; entry.screenNailTask = mThreadPool.submit( new ScreenNailJob(item), new ScreenNailListener(item.getDataVersion())); // request screen nail return entry.screenNailTask; } if (which == BIT_FULL_IMAGE && (entry.requestedBits & BIT_FULL_IMAGE) == 0 && (item.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0) { entry.requestedBits |= BIT_FULL_IMAGE; entry.fullImageTask = mThreadPool.submit( item.requestLargeImage(), new FullImageListener(item.getDataVersion())); // request full image return entry.fullImageTask; } return null; }
private void updateImageCache() { HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet()); for (int i = mActiveStart; i < mActiveEnd; ++i) { MediaItem item = mData[i % DATA_CACHE_SIZE]; long version = item == null ? MediaObject.INVALID_DATA_VERSION : item.getDataVersion(); if (version == MediaObject.INVALID_DATA_VERSION) continue; ImageEntry entry = mImageCache.get(version); toBeRemoved.remove(version); if (entry != null) { if (Math.abs(i - mCurrentIndex) > 1) { if (entry.fullImageTask != null) { entry.fullImageTask.cancel(); entry.fullImageTask = null; } entry.fullImage = null; entry.requestedBits &= ~BIT_FULL_IMAGE; } } else { entry = new ImageEntry(); entry.rotation = item.getFullImageRotation(); mImageCache.put(version, entry); } } // Clear the data and requests for ImageEntries outside the new window. for (Long version : toBeRemoved) { ImageEntry entry = mImageCache.remove(version); if (entry.fullImageTask != null) entry.fullImageTask.cancel(); if (entry.screenNailTask != null) entry.screenNailTask.cancel(); } }
private void updateImageRequests() { if (!mIsActive) return; int currentIndex = mCurrentIndex; MediaItem item = mData[currentIndex % DATA_CACHE_SIZE]; if (item == null || item.getPath() != mItemPath) { // current item mismatch - don't request image return; } // 1. Find the most wanted request and start it (if not already started). Future<?> task = null; for (int i = 0; i < sImageFetchSeq.length; i++) { int offset = sImageFetchSeq[i].indexOffset; int bit = sImageFetchSeq[i].imageBit; task = startTaskIfNeeded(currentIndex + offset, bit); if (task != null) break; } // 2. Cancel everything else. for (ImageEntry entry : mImageCache.values()) { if (entry.screenNailTask != null && entry.screenNailTask != task) { entry.screenNailTask.cancel(); entry.screenNailTask = null; entry.requestedBits &= ~BIT_SCREEN_NAIL; } if (entry.fullImageTask != null && entry.fullImageTask != task) { entry.fullImageTask.cancel(); entry.fullImageTask = null; entry.requestedBits &= ~BIT_FULL_IMAGE; } } }
/** * Returns a list of images that fulfill the given criteria. Default setting is to return untagged * images, but may be overwritten. * * @param exif also returns images with exif-gps info * @param tagged also returns tagged images * @return matching images */ private List<ImageEntry> getSortedImgList(boolean exif, boolean tagged) { if (yLayer.data == null) { return Collections.emptyList(); } List<ImageEntry> dateImgLst = new ArrayList<>(yLayer.data.size()); for (ImageEntry e : yLayer.data) { if (!e.hasExifTime()) { continue; } if (e.getExifCoor() != null && !exif) { continue; } if (e.isTagged() && e.getExifCoor() == null && !tagged) { continue; } dateImgLst.add(e); } Collections.sort( dateImgLst, new Comparator<ImageEntry>() { @Override public int compare(ImageEntry arg0, ImageEntry arg1) { return arg0.getExifTime().compareTo(arg1.getExifTime()); } }); return dateImgLst; }
public List<ImageEntry> getImages() { List<ImageEntry> copy = new ArrayList<ImageEntry>(); for (ImageEntry ie : data) { copy.add(ie.clone()); } return copy; }
/** * Returns the image that matches the position of the mouse event. * * @param evt Mouse event * @return Image at mouse position, or {@code null} if there is no image at the mouse position * @since 6392 */ public ImageEntry getPhotoUnderMouse(MouseEvent evt) { if (data != null) { for (int idx = data.size() - 1; idx >= 0; --idx) { ImageEntry img = data.get(idx); if (img.getPos() == null) { continue; } Point p = Main.map.mapView.getPoint(img.getPos()); Rectangle r; if (useThumbs && img.hasThumbnail()) { Dimension d = scaledDimension(img.getThumbnail()); r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); } else { r = new Rectangle( p.x - icon.getIconWidth() / 2, p.y - icon.getIconHeight() / 2, icon.getIconWidth(), icon.getIconHeight()); } if (r.contains(evt.getPoint())) { return img; } } } return null; }
@Override protected void finish() { if (!errorMessages.isEmpty()) { JOptionPane.showMessageDialog( Main.parent, formatErrorMessages(), tr("Error"), JOptionPane.ERROR_MESSAGE); } if (layer != null) { Main.main.addLayer(layer); layer.hook_up_mouse_events(); // Main.map.mapView should exist // now. Can add mouse listener Main.map.mapView.addPropertyChangeListener(layer); if (Main.map.getToggleDialog(ImageViewerDialog.class) == null) { ImageViewerDialog.newInstance(); Main.map.addToggleDialog(ImageViewerDialog.getInstance()); } if (!cancelled && layer.data.size() > 0) { boolean noGeotagFound = true; for (ImageEntry e : layer.data) { if (e.getPos() != null) { noGeotagFound = false; } } if (noGeotagFound) { new CorrelateGpxWithImages(layer).actionPerformed(null); } } } }
private void updateScreenNail(long version, Future<Bitmap> future) { ImageEntry entry = mImageCache.get(version); if (entry == null || entry.screenNailTask != future) { Bitmap screenNail = future.get(); if (screenNail != null) screenNail.recycle(); return; } entry.screenNailTask = null; entry.screenNail = future.get(); if (entry.screenNail == null) { entry.failToLoad = true; /*a@nufront start*/ for (int i = -1; i <= 1; ++i) { if (version == getVersion(mCurrentIndex + i)) { if (0 == i) updateTileProvider(entry); mPhotoView.notifyImageInvalidated(i); } } /*a@nufront end*/ } else { if (mDataListener != null) { mDataListener.onPhotoAvailable(version, false); } for (int i = -1; i <= 1; ++i) { if (version == getVersion(mCurrentIndex + i)) { if (i == 0) updateTileProvider(entry); mPhotoView.notifyImageInvalidated(i); } } } updateImageRequests(); }
private FetchEntry getFetchEntry(String urlString, URL url) { ImageEntry imageEntry = new ImageEntry(urlString); String hash = toHexString(shaHash.get().digest(urlString.getBytes(ClientConfiguration.UTF8_CHARSET))); String cacheFileName = getCacheFileName(hash, urlString); imageEntry.cacheFile = Paths.get(cacheFileName); // from url FetchEntry fetchEntry = new FetchEntry(imageEntry); fetchEntry.url = url; // from cache Path cachePath = Paths.get(cacheFileName); if (Files.exists(cachePath)) { FetchEntry cacheFetchEntry = new FetchEntry(imageEntry); try { cacheFetchEntry.url = cachePath.toUri().toURL(); } catch (MalformedURLException e) { throw new AssertionError(e); } cacheFetchEntry.setAlternateEntry(fetchEntry); fetchEntry = cacheFetchEntry; } else { logger.debug("Cache miss: {}", urlString); } return fetchEntry; }
/** * Prepare the string that is displayed if layer information is requested. * * @return String with layer information */ private String infoText() { int tagged = 0; int newdata = 0; int n = 0; if (data != null) { n = data.size(); for (ImageEntry e : data) { if (e.getPos() != null) { tagged++; } if (e.hasNewGpsData()) { newdata++; } } } return "<html>" + trn("{0} image loaded.", "{0} images loaded.", n, n) + ' ' + trn("{0} was found to be GPS tagged.", "{0} were found to be GPS tagged.", tagged, tagged) + (newdata > 0 ? "<br>" + trn("{0} has updated GPS data.", "{0} have updated GPS data.", newdata, newdata) : "") + "</html>"; }
@Override public void windowDeactivated(WindowEvent e) { int result = checkAndSave(); switch (result) { case NOTHING: break; case CANCEL: if (yLayer != null) { if (yLayer.data != null) { for (ImageEntry ie : yLayer.data) { ie.discardTmp(); } } yLayer.updateBufferAndRepaint(); } break; case AGAIN: actionPerformed(null); break; case DONE: Main.pref.put("geoimage.timezone", formatTimezone(timezone)); Main.pref.put("geoimage.delta", Long.toString(delta * 1000)); Main.pref.put("geoimage.showThumbs", yLayer.useThumbs); yLayer.useThumbs = cbShowThumbs.isSelected(); yLayer.startLoadThumbs(); // Search whether an other layer has yet defined some bounding box. // If none, we'll zoom to the bounding box of the layer with the photos. boolean boundingBoxedLayerFound = false; for (Layer l : Main.map.mapView.getAllLayers()) { if (l != yLayer) { BoundingXYVisitor bbox = new BoundingXYVisitor(); l.visitBoundingBox(bbox); if (bbox.getBounds() != null) { boundingBoxedLayerFound = true; break; } } } if (!boundingBoxedLayerFound) { BoundingXYVisitor bbox = new BoundingXYVisitor(); yLayer.visitBoundingBox(bbox); Main.map.mapView.zoomTo(bbox); } if (yLayer.data != null) { for (ImageEntry ie : yLayer.data) { ie.applyTmp(); } } yLayer.updateBufferAndRepaint(); break; default: throw new IllegalStateException(); } }
private String infoText() { int i = 0; for (ImageEntry e : data) if (e.getPos() != null) { i++; } return trn("{0} image loaded.", "{0} images loaded.", data.size(), data.size()) + " " + trn("{0} was found to be GPS tagged.", "{0} were found to be GPS tagged.", i, i); }
public void removeCurrentPhotoFromDisk() { ImageEntry toDelete; if (data != null && !data.isEmpty() && currentPhoto >= 0 && currentPhoto < data.size()) { toDelete = data.get(currentPhoto); int result = new ExtendedDialog( Main.parent, tr("Delete image file from disk"), new String[] {tr("Cancel"), tr("Delete")}) .setButtonIcons(new String[] {"cancel", "dialogs/delete"}) .setContent( new JLabel( tr( "<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>", toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"), SwingConstants.LEFT)) .toggleEnable("geoimage.deleteimagefromdisk") .setCancelButton(1) .setDefaultButton(2) .showDialog() .getValue(); if (result == 2) { data.remove(currentPhoto); if (currentPhoto >= data.size()) { currentPhoto = data.size() - 1; } if (currentPhoto >= 0) { ImageViewerDialog.showImage(this, data.get(currentPhoto)); } else { ImageViewerDialog.showImage(this, null); } if (Utils.deleteFile(toDelete.getFile())) { Main.info("File " + toDelete.getFile() + " deleted. "); } else { JOptionPane.showMessageDialog( Main.parent, tr("Image file could not be deleted."), tr("Error"), JOptionPane.ERROR_MESSAGE); } updateOffscreenBuffer = true; Main.map.repaint(); } } }
/** * 画像を取得する。 * * @param entry イメージエントリ * @throws java.lang.InterruptedException interrupted */ protected void fetchImage(FetchEntry entry) throws InterruptedException { synchronized (entry) { if (entry.isFinished()) { return; } byte[] imageData = NetworkSupport.fetchContents(entry.connectionInfo); ImageEntry imageEntry = entry.imageEntry; imageEntry.rawData = imageData; imageEntry.image = Toolkit.getDefaultToolkit().createImage(imageData); cachedImages.put(entry.getImageUrl(), entry.imageEntry); configuration.addJob(JobQueue.PRIORITY_IDLE, new ImageFlusher(imageEntry)); } }
@Override public void mergeFrom(Layer from) { GeoImageLayer l = (GeoImageLayer) from; // Stop to load thumbnails on both layers. Thumbnail loading will continue the next time // the layer is painted. stopLoadThumbs(); l.stopLoadThumbs(); final ImageEntry selected = l.data != null && l.currentPhoto >= 0 ? l.data.get(l.currentPhoto) : null; if (l.data != null) { data.addAll(l.data); } Collections.sort(data); // Supress the double photos. if (data.size() > 1) { ImageEntry cur; ImageEntry prev = data.get(data.size() - 1); for (int i = data.size() - 2; i >= 0; i--) { cur = data.get(i); if (cur.getFile().equals(prev.getFile())) { data.remove(i); } else { prev = cur; } } } if (selected != null && !data.isEmpty()) { GuiHelper.runInEDTAndWait( new Runnable() { @Override public void run() { for (int i = 0; i < data.size(); i++) { if (selected.equals(data.get(i))) { currentPhoto = i; ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i)); break; } } } }); } setName(l.getName()); thumbsLoaded &= l.thumbsLoaded; }
/** set the value for the specified cell */ public void setValueAt(final Object value, final int row, final int column) { final List<ImageEntry> entries = _imageEntries; if (row >= entries.size()) return; final ImageEntry entry = entries.get(row); switch (column) { case TITLE_COLUMN: entry.setTitle(value.toString()); break; default: break; } }
/** get the value for the specified cell */ public Object getValueAt(final int row, final int column) { final List<ImageEntry> entries = _imageEntries; if (row >= entries.size()) return null; final ImageEntry entry = entries.get(row); switch (column) { case FILE_NAME_COLUMN: return entry.getImageFile().getName(); case TITLE_COLUMN: return entry.getTitle(); default: return null; } }
@Override protected void realRun() throws IOException { progressMonitor.subTask(tr("Starting directory scan")); Collection<File> files = new ArrayList<File>(); try { addRecursiveFiles(files, selection); } catch (NullPointerException npe) { rememberError(tr("One of the selected files was null")); } if (cancelled) return; progressMonitor.subTask(tr("Read photos...")); progressMonitor.setTicksCount(files.size()); progressMonitor.subTask(tr("Read photos...")); progressMonitor.setTicksCount(files.size()); // read the image files List<ImageEntry> data = new ArrayList<ImageEntry>(files.size()); for (File f : files) { if (cancelled) { break; } progressMonitor.subTask(tr("Reading {0}...", f.getName())); progressMonitor.worked(1); ImageEntry e = new ImageEntry(); // Changed to silently cope with no time info in exif. One case // of person having time that couldn't be parsed, but valid GPS info try { e.setExifTime(ExifReader.readTime(f)); } catch (ParseException e1) { e.setExifTime(null); } e.setFile(f); extractExif(e); data.add(e); } layer = new GeoImageLayer(data, gpxLayer); files.clear(); }
private void refreshPage() { this.errorMessage.setText(""); this.txtName.setText(""); this.txtURL.setText(""); int fila = 1; for (ImageEntry image : model.getImageEntries()) { this.imageTable.setText(fila, 0, image.getName()); final int row = fila; final Image newImage = new Image(image.getURL()); newImage.addClickHandler( new ClickHandler() { @Override public void onClick(ClickEvent event) { selectImageEntry(imageTable.getText(row, 0)); } }); this.imageTable.setWidget(fila, 1, newImage); fila += 1; } }
private void updateFullImage(long version, Future<BitmapRegionDecoder> future) { ImageEntry entry = mImageCache.get(version); if (entry == null || entry.fullImageTask != future) { BitmapRegionDecoder fullImage = future.get(); if (fullImage != null) fullImage.recycle(); return; } entry.fullImageTask = null; entry.fullImage = future.get(); if (entry.fullImage != null) { if (mDataListener != null) { mDataListener.onPhotoAvailable(version, true); } if (version == getVersion(mCurrentIndex)) { updateTileProvider(entry); mPhotoView.notifyImageInvalidated(0); } } updateImageRequests(); }
@Override protected void finish() { if (!errorMessages.isEmpty()) { JOptionPane.showMessageDialog( Main.parent, formatErrorMessages(), tr("Error"), JOptionPane.ERROR_MESSAGE); } if (layer != null) { Main.main.addLayer(layer); if (!canceled && layer.data != null && !layer.data.isEmpty()) { boolean noGeotagFound = true; for (ImageEntry e : layer.data) { if (e.getPos() != null) { noGeotagFound = false; } } if (noGeotagFound) { new CorrelateGpxWithImages(layer).actionPerformed(null); } } } }
@Override public void mergeFrom(Layer from) { GeoImageLayer l = (GeoImageLayer) from; ImageEntry selected = null; if (l.currentPhoto >= 0) { selected = l.data.get(l.currentPhoto); } data.addAll(l.data); Collections.sort(data); // Supress the double photos. if (data.size() > 1) { ImageEntry cur; ImageEntry prev = data.get(data.size() - 1); for (int i = data.size() - 2; i >= 0; i--) { cur = data.get(i); if (cur.getFile().equals(prev.getFile())) { data.remove(i); } else { prev = cur; } } } if (selected != null) { for (int i = 0; i < data.size(); i++) { if (data.get(i) == selected) { currentPhoto = i; ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i)); break; } } } setName(l.getName()); }
@Override protected void realRun() throws IOException { progressMonitor.subTask(tr("Starting directory scan")); Collection<File> files = new ArrayList<>(); try { addRecursiveFiles(files, selection); } catch (IllegalStateException e) { rememberError(e.getMessage()); } if (canceled) return; progressMonitor.subTask(tr("Read photos...")); progressMonitor.setTicksCount(files.size()); progressMonitor.subTask(tr("Read photos...")); progressMonitor.setTicksCount(files.size()); // read the image files List<ImageEntry> entries = new ArrayList<>(files.size()); for (File f : files) { if (canceled) { break; } progressMonitor.subTask(tr("Reading {0}...", f.getName())); progressMonitor.worked(1); ImageEntry e = new ImageEntry(f); e.extractExif(); entries.add(e); } layer = new GeoImageLayer(entries, gpxLayer); files.clear(); }
private String statusText() { try { timezone = parseTimezone(tfTimezone.getText().trim()); delta = parseOffset(tfOffset.getText().trim()); } catch (ParseException e) { return e.getMessage(); } // The selection of images we are about to correlate may have changed. // So reset all images. if (yLayer.data != null) { for (ImageEntry ie : yLayer.data) { ie.discardTmp(); } } // Construct a list of images that have a date, and sort them on the date. List<ImageEntry> dateImgLst = getSortedImgList(); // Create a temporary copy for each image for (ImageEntry ie : dateImgLst) { ie.createTmp(); ie.tmp.setPos(null); } GpxDataWrapper selGpx = selectedGPX(false); if (selGpx == null) return tr("No gpx selected"); final long offset_ms = ((long) (timezone * 3600) + delta) * 1000; // in milliseconds lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offset_ms); return trn( "<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>", "<html>Matched <b>{0}</b> of <b>{1}</b> photos to GPX track.</html>", dateImgLst.size(), lastNumMatched, dateImgLst.size()); }
private void selectImageEntry(String imageName) { ImageEntry entry = model.getImageByName(imageName); txtName.setText(entry.getName()); txtURL.setText(entry.getURL()); }
@Override public void paint(Graphics2D g, MapView mv, Bounds bounds) { int width = Main.map.mapView.getWidth(); int height = Main.map.mapView.getHeight(); Rectangle clip = g.getClipBounds(); if (useThumbs) { if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible || offscreenBuffer.getHeight() != height) { offscreenBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); updateOffscreenBuffer = true; } if (updateOffscreenBuffer) { Graphics2D tempG = offscreenBuffer.createGraphics(); tempG.setColor(new Color(0, 0, 0, 0)); Composite saveComp = tempG.getComposite(); tempG.setComposite(AlphaComposite.Clear); // remove the old images tempG.fillRect(0, 0, width, height); tempG.setComposite(saveComp); for (ImageEntry e : data) { if (e.getPos() == null) { continue; } Point p = mv.getPoint(e.getPos()); if (e.thumbnail != null) { Dimension d = scaledDimension(e.thumbnail); Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); if (clip.intersects(target)) { tempG.drawImage(e.thumbnail, target.x, target.y, target.width, target.height, null); } } else { // thumbnail not loaded yet icon.paintIcon( mv, tempG, p.x - icon.getIconWidth() / 2, p.y - icon.getIconHeight() / 2); } } updateOffscreenBuffer = false; } g.drawImage(offscreenBuffer, 0, 0, null); } else { for (ImageEntry e : data) { if (e.getPos() == null) { continue; } Point p = mv.getPoint(e.getPos()); icon.paintIcon(mv, g, p.x - icon.getIconWidth() / 2, p.y - icon.getIconHeight() / 2); } } if (currentPhoto >= 0 && currentPhoto < data.size()) { ImageEntry e = data.get(currentPhoto); if (e.getPos() != null) { Point p = mv.getPoint(e.getPos()); if (e.thumbnail != null) { Dimension d = scaledDimension(e.thumbnail); g.setColor(new Color(128, 0, 0, 122)); g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); } else { if (e.getExifImgDir() != null) { double arrowlength = 25; double arrowwidth = 18; double dir = e.getExifImgDir(); // Rotate 90 degrees CCW double headdir = (dir < 90) ? dir + 270 : dir - 90; double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90; double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90; double ptx = p.x + Math.cos(Math.toRadians(headdir)) * arrowlength; double pty = p.y + Math.sin(Math.toRadians(headdir)) * arrowlength; double ltx = p.x + Math.cos(Math.toRadians(leftdir)) * arrowwidth / 2; double lty = p.y + Math.sin(Math.toRadians(leftdir)) * arrowwidth / 2; double rtx = p.x + Math.cos(Math.toRadians(rightdir)) * arrowwidth / 2; double rty = p.y + Math.sin(Math.toRadians(rightdir)) * arrowwidth / 2; g.setColor(Color.white); int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx}; int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty}; g.fillPolygon(xar, yar, 4); } selectedIcon.paintIcon( mv, g, p.x - selectedIcon.getIconWidth() / 2, p.y - selectedIcon.getIconHeight() / 2); } } } }
static int matchPoints( List<ImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime, long offset) { // Time between the track point and the previous one, 5 sec if first point, i.e. photos take // 5 sec before the first track point can be assumed to be take at the starting position long interval = prevWpTime > 0 ? Math.abs(curWpTime - prevWpTime) : 5 * 1000; int ret = 0; // i is the index of the timewise last photo that has the same or earlier EXIF time int i = getLastIndexOfListBefore(images, curWpTime); // no photos match if (i < 0) return 0; Double speed = null; Double prevElevation = null; if (prevWp != null) { double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor()); // This is in km/h, 3.6 * m/s if (curWpTime > prevWpTime) { speed = 3600 * distance / (curWpTime - prevWpTime); } prevElevation = getElevation(prevWp); } Double curElevation = getElevation(curWp); // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds // before the first point will be geotagged with the starting point if (prevWpTime == 0 || curWpTime <= prevWpTime) { while (i >= 0) { final ImageEntry curImg = images.get(i); long time = curImg.getExifTime().getTime(); if (time > curWpTime || time < curWpTime - interval) { break; } if (curImg.tmp.getPos() == null) { curImg.tmp.setPos(curWp.getCoor()); curImg.tmp.setSpeed(speed); curImg.tmp.setElevation(curElevation); curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset)); curImg.flagNewGpsData(); ret++; } i--; } return ret; } // This code gives a simple linear interpolation of the coordinates between current and // previous track point assuming a constant speed in between while (i >= 0) { ImageEntry curImg = images.get(i); long imgTime = curImg.getExifTime().getTime(); if (imgTime < prevWpTime) { break; } if (curImg.tmp.getPos() == null && prevWp != null) { // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless // variable double timeDiff = (double) (imgTime - prevWpTime) / interval; curImg.tmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff)); curImg.tmp.setSpeed(speed); if (curElevation != null && prevElevation != null) { curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff); } curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset)); curImg.flagNewGpsData(); ret++; } i--; } return ret; }
private static void extractExif(ImageEntry e) { double deg; double min, sec; double lon, lat; Metadata metadata = null; Directory dir = null; try { metadata = JpegMetadataReader.readMetadata(e.getFile()); dir = metadata.getDirectory(GpsDirectory.class); } catch (CompoundException p) { e.setExifCoor(null); e.setPos(null); return; } try { // longitude Rational[] components = dir.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE); deg = components[0].doubleValue(); min = components[1].doubleValue(); sec = components[2].doubleValue(); if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec)) throw new IllegalArgumentException(); lon = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600))); if (dir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W') { lon = -lon; } // latitude components = dir.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE); deg = components[0].doubleValue(); min = components[1].doubleValue(); sec = components[2].doubleValue(); if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec)) throw new IllegalArgumentException(); lat = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600))); if (Double.isNaN(lat)) throw new IllegalArgumentException(); if (dir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S') { lat = -lat; } // Store values e.setExifCoor(new LatLon(lat, lon)); e.setPos(e.getExifCoor()); } catch (CompoundException p) { // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220) try { Double longitude = dir.getDouble(GpsDirectory.TAG_GPS_LONGITUDE); Double latitude = dir.getDouble(GpsDirectory.TAG_GPS_LATITUDE); if (longitude == null || latitude == null) throw new CompoundException(""); // Store values e.setExifCoor(new LatLon(latitude, longitude)); e.setPos(e.getExifCoor()); } catch (CompoundException ex) { e.setExifCoor(null); e.setPos(null); } } catch (Exception ex) { // (other exceptions, e.g. #5271) System.err.println("Error when reading EXIF from file: " + ex); e.setExifCoor(null); e.setPos(null); } // compass direction value Rational direction = null; try { direction = dir.getRational(GpsDirectory.TAG_GPS_IMG_DIRECTION); if (direction != null) { e.setExifImgDir(direction.doubleValue()); } } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271) // Do nothing } }
@Override public void paint(Graphics2D g, MapView mv, Bounds bounds) { int width = mv.getWidth(); int height = mv.getHeight(); Rectangle clip = g.getClipBounds(); if (useThumbs) { if (!thumbsLoaded) { startLoadThumbs(); } if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible || offscreenBuffer.getHeight() != height) { offscreenBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); updateOffscreenBuffer = true; } if (updateOffscreenBuffer) { Graphics2D tempG = offscreenBuffer.createGraphics(); tempG.setColor(new Color(0, 0, 0, 0)); Composite saveComp = tempG.getComposite(); tempG.setComposite(AlphaComposite.Clear); // remove the old images tempG.fillRect(0, 0, width, height); tempG.setComposite(saveComp); if (data != null) { for (ImageEntry e : data) { if (e.getPos() == null) { continue; } Point p = mv.getPoint(e.getPos()); if (e.hasThumbnail()) { Dimension d = scaledDimension(e.getThumbnail()); Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height); if (clip.intersects(target)) { tempG.drawImage( e.getThumbnail(), target.x, target.y, target.width, target.height, null); } } else { // thumbnail not loaded yet icon.paintIcon( mv, tempG, p.x - icon.getIconWidth() / 2, p.y - icon.getIconHeight() / 2); } } } updateOffscreenBuffer = false; } g.drawImage(offscreenBuffer, 0, 0, null); } else if (data != null) { for (ImageEntry e : data) { if (e.getPos() == null) { continue; } Point p = mv.getPoint(e.getPos()); icon.paintIcon(mv, g, p.x - icon.getIconWidth() / 2, p.y - icon.getIconHeight() / 2); } } if (currentPhoto >= 0 && currentPhoto < data.size()) { ImageEntry e = data.get(currentPhoto); if (e.getPos() != null) { Point p = mv.getPoint(e.getPos()); int imgWidth; int imgHeight; if (useThumbs && e.hasThumbnail()) { Dimension d = scaledDimension(e.getThumbnail()); imgWidth = d.width; imgHeight = d.height; } else { imgWidth = selectedIcon.getIconWidth(); imgHeight = selectedIcon.getIconHeight(); } if (e.getExifImgDir() != null) { // Multiplier must be larger than sqrt(2)/2=0.71. double arrowlength = Math.max(25, Math.max(imgWidth, imgHeight) * 0.85); double arrowwidth = arrowlength / 1.4; double dir = e.getExifImgDir(); // Rotate 90 degrees CCW double headdir = (dir < 90) ? dir + 270 : dir - 90; double leftdir = (headdir < 90) ? headdir + 270 : headdir - 90; double rightdir = (headdir > 270) ? headdir - 270 : headdir + 90; double ptx = p.x + Math.cos(Math.toRadians(headdir)) * arrowlength; double pty = p.y + Math.sin(Math.toRadians(headdir)) * arrowlength; double ltx = p.x + Math.cos(Math.toRadians(leftdir)) * arrowwidth / 2; double lty = p.y + Math.sin(Math.toRadians(leftdir)) * arrowwidth / 2; double rtx = p.x + Math.cos(Math.toRadians(rightdir)) * arrowwidth / 2; double rty = p.y + Math.sin(Math.toRadians(rightdir)) * arrowwidth / 2; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(new Color(255, 255, 255, 192)); int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx}; int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty}; g.fillPolygon(xar, yar, 4); g.setColor(Color.black); g.setStroke(new BasicStroke(1.2f)); g.drawPolyline(xar, yar, 3); } if (useThumbs && e.hasThumbnail()) { g.setColor(new Color(128, 0, 0, 122)); g.fillRect(p.x - imgWidth / 2, p.y - imgHeight / 2, imgWidth, imgHeight); } else { selectedIcon.paintIcon(mv, g, p.x - imgWidth / 2, p.y - imgHeight / 2); } } } }
@Override public void visitBoundingBox(BoundingXYVisitor v) { for (ImageEntry e : data) { v.visit(e.getPos()); } }