|
@@ -0,0 +1,452 @@
|
|
|
|
|
+package cn.com.yusys.producer.util;
|
|
|
|
|
+
|
|
|
|
|
+import com.coremedia.iso.IsoFile;
|
|
|
|
|
+import com.coremedia.iso.boxes.MovieHeaderBox;
|
|
|
|
|
+import com.coremedia.iso.boxes.MovieBox;
|
|
|
|
|
+import com.drew.imaging.ImageMetadataReader;
|
|
|
|
|
+import com.drew.lang.GeoLocation;
|
|
|
|
|
+import com.drew.metadata.Metadata;
|
|
|
|
|
+import com.drew.metadata.Tag;
|
|
|
|
|
+import com.drew.metadata.exif.ExifIFD0Directory;
|
|
|
|
|
+import com.drew.metadata.exif.ExifSubIFDDirectory;
|
|
|
|
|
+import com.drew.metadata.exif.GpsDirectory;
|
|
|
|
|
+import com.lowagie.text.pdf.PdfReader;
|
|
|
|
|
+import org.jaudiotagger.audio.AudioFile;
|
|
|
|
|
+import org.jaudiotagger.audio.AudioFileIO;
|
|
|
|
|
+import org.jaudiotagger.audio.AudioHeader;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.nio.file.Paths;
|
|
|
|
|
+import java.nio.file.attribute.BasicFileAttributes;
|
|
|
|
|
+import java.time.Instant;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.regex.Matcher;
|
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
+import javax.imageio.ImageIO;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * File metadata extractor.
|
|
|
|
|
+ * 融合 OS 基础属性与轻量内容元数据提取(不依赖 PDFBox)。
|
|
|
|
|
+ */
|
|
|
|
|
+public final class FileMetadataUtil {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Pattern FIRST_NUMBER_PATTERN = Pattern.compile("([-+]?[0-9]*\\.?[0-9]+)");
|
|
|
|
|
+
|
|
|
|
|
+ private FileMetadataUtil() {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Map<String, Object> extract(Path path) throws IOException {
|
|
|
|
|
+ if (path == null) {
|
|
|
|
|
+ throw new IllegalArgumentException("path is null");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path absolutePath = path.toAbsolutePath();
|
|
|
|
|
+ Map<String, Object> metadataMap = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 提取操作系统层级的基础元数据 (保留了你原有的优秀逻辑)
|
|
|
|
|
+ extractOsMetadata(absolutePath, metadataMap);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 提取内容层级的轻量元数据(PDF / 图片)
|
|
|
|
|
+ extractContentMetadata(absolutePath, metadataMap);
|
|
|
|
|
+
|
|
|
|
|
+ return metadataMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Map<String, Object> extract(String path) throws IOException {
|
|
|
|
|
+ return extract(Paths.get(path));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 提取操作系统层面的基础文件属性
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void extractOsMetadata(Path absolutePath, Map<String, Object> metadata) throws IOException {
|
|
|
|
|
+ BasicFileAttributes attrs = Files.readAttributes(absolutePath, BasicFileAttributes.class);
|
|
|
|
|
+
|
|
|
|
|
+ String fileName = absolutePath.getFileName() == null ? absolutePath.toString() : absolutePath.getFileName().toString();
|
|
|
|
|
+ String extension = FileTypeDetector.getExtension(fileName);
|
|
|
|
|
+ String mimeType = Files.probeContentType(absolutePath);
|
|
|
|
|
+
|
|
|
|
|
+ metadata.put("fileName", fileName);
|
|
|
|
|
+ metadata.put("extension", extension);
|
|
|
|
|
+ metadata.put("absolutePath", absolutePath.toString());
|
|
|
|
|
+ metadata.put("sizeBytes", attrs.size());
|
|
|
|
|
+ metadata.put("mimeType", mimeType);
|
|
|
|
|
+ metadata.put("category", FileTypeDetector.detectCategoryLabel(absolutePath));
|
|
|
|
|
+ metadata.put("createdAt", toIsoString(attrs.creationTime().toInstant()));
|
|
|
|
|
+ metadata.put("lastModifiedAt", toIsoString(attrs.lastModifiedTime().toInstant()));
|
|
|
|
|
+ metadata.put("lastAccessAt", toIsoString(attrs.lastAccessTime().toInstant()));
|
|
|
|
|
+ metadata.put("isDirectory", attrs.isDirectory());
|
|
|
|
|
+ metadata.put("isRegularFile", attrs.isRegularFile());
|
|
|
|
|
+ metadata.put("isReadable", Files.isReadable(absolutePath));
|
|
|
|
|
+ metadata.put("isWritable", Files.isWritable(absolutePath));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 使用轻量方式提取内容层元数据(避免 PDFBox)
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void extractContentMetadata(Path absolutePath, Map<String, Object> metadataMap) {
|
|
|
|
|
+ if (!Files.isRegularFile(absolutePath) || !Files.isReadable(absolutePath)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String mimeType = (String) metadataMap.get("mimeType");
|
|
|
|
|
+ String extension = (String) metadataMap.get("extension");
|
|
|
|
|
+
|
|
|
|
|
+ if (isPdf(mimeType, extension)) {
|
|
|
|
|
+ extractPdfMetadata(absolutePath, metadataMap);
|
|
|
|
|
+ } else if (isImage(absolutePath, mimeType)) {
|
|
|
|
|
+ extractImageMetadata(absolutePath, metadataMap);
|
|
|
|
|
+ } else if (isAudio(absolutePath, mimeType)) {
|
|
|
|
|
+ extractAudioMetadata(absolutePath, metadataMap);
|
|
|
|
|
+ } else if (isVideo(absolutePath, mimeType)) {
|
|
|
|
|
+ extractVideoMetadata(absolutePath, metadataMap, mimeType, extension);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isPdf(String mimeType, String extension) {
|
|
|
|
|
+ if (mimeType != null && "application/pdf".equalsIgnoreCase(mimeType)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return "pdf".equalsIgnoreCase(extension);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isImage(Path absolutePath, String mimeType) {
|
|
|
|
|
+ if (mimeType != null && mimeType.toLowerCase().startsWith("image/")) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return FileTypeDetector.detectCategory(absolutePath) == FileTypeDetector.Category.IMAGE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isAudio(Path absolutePath, String mimeType) {
|
|
|
|
|
+ if (mimeType != null && mimeType.toLowerCase().startsWith("audio/")) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return FileTypeDetector.detectCategory(absolutePath) == FileTypeDetector.Category.AUDIO;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isVideo(Path absolutePath, String mimeType) {
|
|
|
|
|
+ if (mimeType != null && mimeType.toLowerCase().startsWith("video/")) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return FileTypeDetector.detectCategory(absolutePath) == FileTypeDetector.Category.VIDEO;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void extractPdfMetadata(Path absolutePath, Map<String, Object> metadataMap) {
|
|
|
|
|
+ try (InputStream stream = Files.newInputStream(absolutePath)) {
|
|
|
|
|
+ PdfReader reader = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ reader = new PdfReader(stream);
|
|
|
|
|
+ Map<String, String> info = reader.getInfo();
|
|
|
|
|
+ Map<String, Object> pdfInfo = new LinkedHashMap<>();
|
|
|
|
|
+ if (info != null) {
|
|
|
|
|
+ pdfInfo.putAll(info);
|
|
|
|
|
+ }
|
|
|
|
|
+ pdfInfo.put("numberOfPages", reader.getNumberOfPages());
|
|
|
|
|
+ metadataMap.put("pdfMetadata", pdfInfo);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (reader != null) {
|
|
|
|
|
+ reader.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ metadataMap.put("pdfMetadataError", "Parse skipped or failed: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void extractImageMetadata(Path absolutePath, Map<String, Object> metadataMap) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ BufferedImage image = ImageIO.read(absolutePath.toFile());
|
|
|
|
|
+ if (image == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> imageInfo = new LinkedHashMap<>();
|
|
|
|
|
+ imageInfo.put("width", image.getWidth());
|
|
|
|
|
+ imageInfo.put("height", image.getHeight());
|
|
|
|
|
+
|
|
|
|
|
+ Metadata metadata = ImageMetadataReader.readMetadata(absolutePath.toFile());
|
|
|
|
|
+ ExifSubIFDDirectory exifSub = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
|
|
|
|
|
+ ExifIFD0Directory exifIfd0 = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
|
|
|
|
+ GpsDirectory gps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
|
|
|
|
|
+
|
|
|
|
|
+ Date originalDate = exifSub != null ? exifSub.getDateOriginal() : null;
|
|
|
|
|
+ if (originalDate != null) {
|
|
|
|
|
+ imageInfo.put("shootingTime", toIsoString(originalDate.toInstant()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (exifIfd0 != null) {
|
|
|
|
|
+ String make = exifIfd0.getString(ExifIFD0Directory.TAG_MAKE);
|
|
|
|
|
+ String model = exifIfd0.getString(ExifIFD0Directory.TAG_MODEL);
|
|
|
|
|
+ if (make != null) {
|
|
|
|
|
+ imageInfo.put("cameraMake", make);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (model != null) {
|
|
|
|
|
+ imageInfo.put("cameraModel", model);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (gps != null) {
|
|
|
|
|
+ GeoLocation geoLocation = gps.getGeoLocation();
|
|
|
|
|
+ if (geoLocation != null && !geoLocation.isZero()) {
|
|
|
|
|
+ imageInfo.put("gpsLatitude", geoLocation.getLatitude());
|
|
|
|
|
+ imageInfo.put("gpsLongitude", geoLocation.getLongitude());
|
|
|
|
|
+ }
|
|
|
|
|
+ String altitudeDesc = gps.getString(GpsDirectory.TAG_ALTITUDE);
|
|
|
|
|
+ if (altitudeDesc != null) {
|
|
|
|
|
+ Double altitudeValue = parseFirstNumber(altitudeDesc);
|
|
|
|
|
+ if (altitudeValue != null) {
|
|
|
|
|
+ imageInfo.put("altitudeMeters", altitudeValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ metadataMap.put("imageMetadata", imageInfo);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ metadataMap.put("imageMetadataError", "Parse skipped or failed: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void extractAudioMetadata(Path absolutePath, Map<String, Object> metadataMap) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ AudioFile audioFile = AudioFileIO.read(absolutePath.toFile());
|
|
|
|
|
+ AudioHeader header = audioFile.getAudioHeader();
|
|
|
|
|
+ if (header == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> audioInfo = new LinkedHashMap<>();
|
|
|
|
|
+ int durationSeconds = header.getTrackLength();
|
|
|
|
|
+ audioInfo.put("durationSeconds", durationSeconds);
|
|
|
|
|
+ audioInfo.put("durationMillis", durationSeconds * 1000L);
|
|
|
|
|
+ audioInfo.put("bitRate", header.getBitRate());
|
|
|
|
|
+ audioInfo.put("sampleRate", header.getSampleRate());
|
|
|
|
|
+ audioInfo.put("channels", header.getChannels());
|
|
|
|
|
+ audioInfo.put("format", header.getFormat());
|
|
|
|
|
+ metadataMap.put("audioMetadata", audioInfo);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ metadataMap.put("audioMetadataError", "Parse skipped or failed: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isMp4Like(String mimeType, String extension) {
|
|
|
|
|
+ if (mimeType != null) {
|
|
|
|
|
+ String lower = mimeType.toLowerCase();
|
|
|
|
|
+ if (lower.equals("video/mp4") || lower.equals("video/quicktime")) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return "mp4".equalsIgnoreCase(extension) || "m4v".equalsIgnoreCase(extension) || "mov".equalsIgnoreCase(extension);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void extractVideoMetadata(Path absolutePath,
|
|
|
|
|
+ Map<String, Object> metadataMap,
|
|
|
|
|
+ String mimeType,
|
|
|
|
|
+ String extension) {
|
|
|
|
|
+ Map<String, Object> videoInfo = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ if (isMp4Like(mimeType, extension)) {
|
|
|
|
|
+ IsoFile isoFile = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ isoFile = new IsoFile(absolutePath.toString());
|
|
|
|
|
+ MovieBox movieBox = isoFile.getMovieBox();
|
|
|
|
|
+ if (movieBox != null) {
|
|
|
|
|
+ MovieHeaderBox mvhd = movieBox.getMovieHeaderBox();
|
|
|
|
|
+ if (mvhd != null) {
|
|
|
|
|
+ long duration = mvhd.getDuration();
|
|
|
|
|
+ long timescale = mvhd.getTimescale();
|
|
|
|
|
+ if (timescale > 0) {
|
|
|
|
|
+ double seconds = (double) duration / (double) timescale;
|
|
|
|
|
+ long millis = (long) (seconds * 1000.0);
|
|
|
|
|
+ videoInfo.put("durationSeconds", seconds);
|
|
|
|
|
+ videoInfo.put("durationMillis", millis);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mvhd.getCreationTime() != null) {
|
|
|
|
|
+ videoInfo.put("creationTime", mvhd.getCreationTime().toInstant().toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mvhd.getModificationTime() != null) {
|
|
|
|
|
+ videoInfo.put("modificationTime", mvhd.getModificationTime().toInstant().toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ videoInfo.put("mp4ParseError", e.getMessage());
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (isoFile != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ isoFile.close();
|
|
|
|
|
+ } catch (IOException ignore) {
|
|
|
|
|
+ // ignore close failure
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 通用兜底:使用 metadata-extractor 尝试解析拍摄时间/地点/时长
|
|
|
|
|
+ try {
|
|
|
|
|
+ Metadata metadata = ImageMetadataReader.readMetadata(absolutePath.toFile());
|
|
|
|
|
+ Date creationDate = findFirstDate(metadata, "creation", "create date", "media create");
|
|
|
|
|
+ if (creationDate != null) {
|
|
|
|
|
+ String creationIso = creationDate.toInstant().toString();
|
|
|
|
|
+ if (!videoInfo.containsKey("creationTime")) {
|
|
|
|
|
+ videoInfo.put("creationTime", creationIso);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!videoInfo.containsKey("shootingTime")) {
|
|
|
|
|
+ videoInfo.put("shootingTime", creationIso);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String location = findFirstTagString(metadata, "location", "gps");
|
|
|
|
|
+ if (location != null) {
|
|
|
|
|
+ videoInfo.put("location", location);
|
|
|
|
|
+ Double[] latLon = parseIso6709(location);
|
|
|
|
|
+ if (latLon != null) {
|
|
|
|
|
+ videoInfo.put("gpsLatitude", latLon[0]);
|
|
|
|
|
+ videoInfo.put("gpsLongitude", latLon[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Double durationSeconds = findDurationSeconds(metadata);
|
|
|
|
|
+ if (durationSeconds != null && !videoInfo.containsKey("durationSeconds")) {
|
|
|
|
|
+ long millis = (long) (durationSeconds * 1000.0);
|
|
|
|
|
+ videoInfo.put("durationSeconds", durationSeconds);
|
|
|
|
|
+ videoInfo.put("durationMillis", millis);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ videoInfo.put("genericParseError", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!videoInfo.isEmpty()) {
|
|
|
|
|
+ metadataMap.put("videoMetadata", videoInfo);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String toIsoString(Instant instant) {
|
|
|
|
|
+ if (instant == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return instant.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Date findFirstDate(Metadata metadata, String... keywords) {
|
|
|
|
|
+ if (metadata == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (com.drew.metadata.Directory directory : metadata.getDirectories()) {
|
|
|
|
|
+ for (Tag tag : directory.getTags()) {
|
|
|
|
|
+ String tagName = tag.getTagName();
|
|
|
|
|
+ if (tagName == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String lower = tagName.toLowerCase();
|
|
|
|
|
+ for (String key : keywords) {
|
|
|
|
|
+ if (lower.contains(key)) {
|
|
|
|
|
+ Object obj = directory.getObject(tag.getTagType());
|
|
|
|
|
+ if (obj instanceof Date) {
|
|
|
|
|
+ return (Date) obj;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String findFirstTagString(Metadata metadata, String... keywords) {
|
|
|
|
|
+ if (metadata == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (com.drew.metadata.Directory directory : metadata.getDirectories()) {
|
|
|
|
|
+ for (Tag tag : directory.getTags()) {
|
|
|
|
|
+ String tagName = tag.getTagName();
|
|
|
|
|
+ if (tagName == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String lower = tagName.toLowerCase();
|
|
|
|
|
+ for (String key : keywords) {
|
|
|
|
|
+ if (lower.contains(key)) {
|
|
|
|
|
+ return tag.getDescription();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Double findDurationSeconds(Metadata metadata) {
|
|
|
|
|
+ if (metadata == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (com.drew.metadata.Directory directory : metadata.getDirectories()) {
|
|
|
|
|
+ for (Tag tag : directory.getTags()) {
|
|
|
|
|
+ String tagName = tag.getTagName();
|
|
|
|
|
+ if (tagName == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String lower = tagName.toLowerCase();
|
|
|
|
|
+ if (lower.contains("duration")) {
|
|
|
|
|
+ Object obj = directory.getObject(tag.getTagType());
|
|
|
|
|
+ if (obj instanceof Number) {
|
|
|
|
|
+ return ((Number) obj).doubleValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ String desc = tag.getDescription();
|
|
|
|
|
+ Double parsed = parseFirstNumber(desc);
|
|
|
|
|
+ if (parsed != null) {
|
|
|
|
|
+ return parsed;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Double parseFirstNumber(String text) {
|
|
|
|
|
+ if (text == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Matcher matcher = FIRST_NUMBER_PATTERN.matcher(text);
|
|
|
|
|
+ if (!matcher.find()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Double.parseDouble(matcher.group(1));
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Double[] parseIso6709(String location) {
|
|
|
|
|
+ if (location == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String value = location.trim();
|
|
|
|
|
+ if (value.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ int split = -1;
|
|
|
|
|
+ for (int i = 1; i < value.length(); i++) {
|
|
|
|
|
+ char c = value.charAt(i);
|
|
|
|
|
+ if (c == '+' || c == '-') {
|
|
|
|
|
+ split = i;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (split <= 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String latStr = value.substring(0, split);
|
|
|
|
|
+ String lonStr = value.substring(split);
|
|
|
|
|
+ latStr = latStr.replace("/", "");
|
|
|
|
|
+ lonStr = lonStr.replace("/", "");
|
|
|
|
|
+ try {
|
|
|
|
|
+ Double lat = Double.parseDouble(latStr);
|
|
|
|
|
+ Double lon = Double.parseDouble(lonStr);
|
|
|
|
|
+ return new Double[]{lat, lon};
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|