/** * */ package ecologylab.bigsemantics.sensing; import com.drew.metadata.exif.GpsDirectory; import ecologylab.bigsemantics.collecting.SemanticsGlobalScope; import ecologylab.bigsemantics.generated.library.PostalAddress; import ecologylab.bigsemantics.generated.library.search.YahooGeoCodeResult; import ecologylab.bigsemantics.generated.library.search.YahooResult; import ecologylab.bigsemantics.generated.library.search.YahooResultSet; import ecologylab.bigsemantics.metadata.Metadata; import ecologylab.bigsemantics.metadata.builtins.Document; import ecologylab.bigsemantics.metadata.builtins.DocumentClosure; import ecologylab.bigsemantics.metadata.builtins.GisLocation; import ecologylab.generic.Continuation; import ecologylab.generic.Debug; import ecologylab.net.ParsedURL; import ecologylab.serialization.types.scalar.DoubleType; /** * Tools for extracting GIS data from EXIF+ headrs from image files, * and using these to populate appropriate S.IM.PL Metadata. * * @author andruid */ public class GisFeatures extends Debug { public static final MetadataExifFeature GPS_LATITUDE_FEATURE = new MetadataExifFeature( "latitude", GpsDirectory.TAG_GPS_LATITUDE); public static final MetadataExifFeature GPS_ALTITUDE_FEATURE = new MetadataExifFeature( "altitude", GpsDirectory.TAG_GPS_ALTITUDE); public static final MetadataExifFeature GPS_LATITUDE_REF_FEATURE = new MetadataExifFeature( "latitude_ref", GpsDirectory.TAG_GPS_LATITUDE_REF); public static final MetadataExifFeature GPS_LONGITUDE_REF_FEATURE = new MetadataExifFeature( "longitude_ref", GpsDirectory.TAG_GPS_LONGITUDE_REF); public static final MetadataExifFeature GPS_LONGITUDE_FEATURE = new MetadataExifFeature( "longitude", GpsDirectory.TAG_GPS_LONGITUDE); public static final MetadataExifFeature GPS_IMG_DIRECTION_REF_FEATURE = new MetadataExifFeature( "direction_ref", GpsDirectory.TAG_GPS_IMG_DIRECTION_REF); public static final MetadataExifFeature GPS_IMG_DIRECTION_FEATURE = new MetadataExifFeature( "direction", GpsDirectory.TAG_GPS_IMG_DIRECTION); public static final MetadataExifFeature GPS_METADATA_FEATURES[] = { GPS_LATITUDE_FEATURE, GPS_LONGITUDE_FEATURE, GPS_LONGITUDE_REF_FEATURE, GPS_LATITUDE_REF_FEATURE, GPS_ALTITUDE_FEATURE, GPS_IMG_DIRECTION_FEATURE, GPS_IMG_DIRECTION_REF_FEATURE, new MetadataExifFeature("satellites", GpsDirectory.TAG_GPS_SATELLITES), }; double latitude, longitude, altitude, direction; GisFeatures(com.drew.metadata.Directory gpsDir, String gpsLatitudeString) { double converted = convertToDegrees(gpsLatitudeString); String gpsLatitudeRef = GPS_LATITUDE_REF_FEATURE.getStringValue(gpsDir); latitude = "S".equals(gpsLatitudeRef) ? -converted : converted; String gpsLongitudeString = GPS_LONGITUDE_FEATURE.getStringValue(gpsDir); converted = convertToDegrees(gpsLongitudeString); String gpsLongitudeRef = GPS_LONGITUDE_REF_FEATURE.getStringValue(gpsDir); longitude = "W".equals(gpsLongitudeRef) ? -converted : converted; String altitudeString = GPS_ALTITUDE_FEATURE.getStringValue(gpsDir); if (altitudeString != null) altitude = DoubleType.rationalToDouble(altitudeString); String directionString = GPS_IMG_DIRECTION_FEATURE.getStringValue(gpsDir); if (directionString != null) direction = DoubleType.rationalToDouble(directionString); } private double convertToDegrees(String stringDMS) { String[] dms = stringDMS.split(" ", 3); double degrees = rationalToDouble(dms, 0); double minutes = rationalToDouble(dms, 1); double seconds = rationalToDouble(dms, 2); return degrees + (minutes / 60) + (seconds / 3600); } public static double rationalToDouble(String[] dms, int whichRational) { String rationalString = dms[whichRational]; return DoubleType.rationalToDouble(rationalString); } /** * Construct one of these, and use it to get nice dms GPS data from the exif metadata -- * if the GPS stuff is there. * * @param gpsDir * @return An instance of this, or null. */ public static GisFeatures extract(com.drew.metadata.Directory gpsDir) { String gpsLatitudeString = GPS_LATITUDE_FEATURE.getStringValue(gpsDir); return (gpsLatitudeString != null) ? new GisFeatures(gpsDir, gpsLatitudeString) : null; } public static boolean containsGisMixin(final Metadata parent) { return parent.containsMixin(GisLocation.class); } GisLocation extractMixin(Metadata parentMetadata) { GisLocation result = containsGisMixin(parentMetadata) ? parentMetadata.getMixin(GisLocation.class) : new GisLocation(); setMixinFeatures(result); return result; } private void setMixinFeatures(GisLocation result) { result.setLongitude(longitude); result.setLatitude(latitude); result.setAltitude(altitude); result.setDirection(direction); } //TODO Reverse geo-code, to discover place! // http://developer.yahoo.com/geo/placefinder/ // http://where.yahooapis.com/geocode?q=38.898717,-77.035974&gflags=R&appid=[yourappidhere] public static final String YAHOO_REVERSE_GEO = "http://where.yahooapis.com/geocode?gflags=R&q="; public static Metadata extractMixin(com.drew.metadata.Directory gpsDir, final SemanticsGlobalScope semanticsScope, final Metadata parentMetadata) { final GisFeatures gpsFeatures = extract(gpsDir); if (gpsFeatures == null) return null; final GisLocation result = gpsFeatures.extractMixin(parentMetadata); parentMetadata.addMixin(result); ParsedURL reverseGeo = ParsedURL.getAbsolute(YAHOO_REVERSE_GEO + result.getLongitude()+","+result.getLatitude()); Document reverseGeoDoc = semanticsScope.getOrConstructDocument(reverseGeo); DocumentClosure geoClosure = reverseGeoDoc.getOrConstructClosure(); geoClosure.addContinuation(new Continuation<DocumentClosure>() { @Override public void callback(DocumentClosure o) { // TODO Auto-generated method stub YahooResultSet resultSet = (YahooResultSet) o.getDocument(); YahooResult yahooResult = resultSet.getResults().get(0); if (yahooResult instanceof YahooGeoCodeResult) { YahooGeoCodeResult yahooGeoResult = (YahooGeoCodeResult) yahooResult; println(yahooGeoResult.getCountry() + " > " + yahooGeoResult.getCity()); PostalAddress postalAddress = new PostalAddress(semanticsScope.getMetaMetadataRepository().getMMByClass(PostalAddress.class)); postalAddress.setStreetAddress(yahooGeoResult.getLine1()); postalAddress.setLocality(yahooGeoResult.getCity()); postalAddress.setRegion(yahooGeoResult.getState()); postalAddress.setPostalCode(yahooGeoResult.getPostal()); postalAddress.setCounty(yahooGeoResult.getCounty()); postalAddress.setCountry(yahooGeoResult.getCountry()); parentMetadata.addMixin(postalAddress); } else { gpsFeatures.warning("type of results is not YahooGeoResult! " + yahooResult); } } }); geoClosure.queueDownload(); return result; } }