geotiff: locating, map overlay
This commit is contained in:
parent
1f99117f12
commit
f34dca8019
24 changed files with 1138 additions and 81 deletions
|
@ -8,40 +8,44 @@ https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
|
|||
https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFFphotoshop.pdf
|
||||
*/
|
||||
object ExifTags {
|
||||
private const val TAG_X_POSITION = 0x011e
|
||||
private const val TAG_Y_POSITION = 0x011f
|
||||
private const val TAG_T4_OPTIONS = 0x0124
|
||||
private const val TAG_T6_OPTIONS = 0x0125
|
||||
private const val TAG_COLOR_MAP = 0x0140
|
||||
private const val TAG_EXTRA_SAMPLES = 0x0152
|
||||
private const val TAG_SAMPLE_FORMAT = 0x0153
|
||||
private const val TAG_RATING_PERCENT = 0x4749
|
||||
private const val X_POSITION = 0x011e
|
||||
private const val Y_POSITION = 0x011f
|
||||
private const val T4_OPTIONS = 0x0124
|
||||
private const val T6_OPTIONS = 0x0125
|
||||
private const val COLOR_MAP = 0x0140
|
||||
private const val EXTRA_SAMPLES = 0x0152
|
||||
private const val SAMPLE_FORMAT = 0x0153
|
||||
private const val SMIN_SAMPLE_VALUE = 0x0154
|
||||
private const val SMAX_SAMPLE_VALUE = 0x0155
|
||||
private const val RATING_PERCENT = 0x4749
|
||||
private const val SONY_RAW_FILE_TYPE = 0x7000
|
||||
private const val SONY_TONE_CURVE = 0x7010
|
||||
private const val TAG_MATTEING = 0x80e3
|
||||
private const val MATTEING = 0x80e3
|
||||
|
||||
// sensing method (0x9217) redundant with sensing method (0xA217)
|
||||
private const val TAG_SENSING_METHOD = 0x9217
|
||||
private const val TAG_IMAGE_SOURCE_DATA = 0x935c
|
||||
private const val TAG_GDAL_METADATA = 0xa480
|
||||
private const val TAG_GDAL_NO_DATA = 0xa481
|
||||
private const val SENSING_METHOD = 0x9217
|
||||
private const val IMAGE_SOURCE_DATA = 0x935c
|
||||
private const val GDAL_METADATA = 0xa480
|
||||
private const val GDAL_NO_DATA = 0xa481
|
||||
|
||||
private val tagNameMap = hashMapOf(
|
||||
TAG_X_POSITION to "X Position",
|
||||
TAG_Y_POSITION to "Y Position",
|
||||
TAG_T4_OPTIONS to "T4 Options",
|
||||
TAG_T6_OPTIONS to "T6 Options",
|
||||
TAG_COLOR_MAP to "Color Map",
|
||||
TAG_EXTRA_SAMPLES to "Extra Samples",
|
||||
TAG_SAMPLE_FORMAT to "Sample Format",
|
||||
TAG_RATING_PERCENT to "Rating Percent",
|
||||
X_POSITION to "X Position",
|
||||
Y_POSITION to "Y Position",
|
||||
T4_OPTIONS to "T4 Options",
|
||||
T6_OPTIONS to "T6 Options",
|
||||
COLOR_MAP to "Color Map",
|
||||
EXTRA_SAMPLES to "Extra Samples",
|
||||
SAMPLE_FORMAT to "Sample Format",
|
||||
SMIN_SAMPLE_VALUE to "S Min Sample Value",
|
||||
SMAX_SAMPLE_VALUE to "S Max Sample Value",
|
||||
RATING_PERCENT to "Rating Percent",
|
||||
SONY_RAW_FILE_TYPE to "Sony Raw File Type",
|
||||
SONY_TONE_CURVE to "Sony Tone Curve",
|
||||
TAG_MATTEING to "Matteing",
|
||||
TAG_SENSING_METHOD to "Sensing Method (0x9217)",
|
||||
TAG_IMAGE_SOURCE_DATA to "Image Source Data",
|
||||
TAG_GDAL_METADATA to "GDAL Metadata",
|
||||
TAG_GDAL_NO_DATA to "GDAL No Data",
|
||||
MATTEING to "Matteing",
|
||||
SENSING_METHOD to "Sensing Method (0x9217)",
|
||||
IMAGE_SOURCE_DATA to "Image Source Data",
|
||||
GDAL_METADATA to "GDAL Metadata",
|
||||
GDAL_NO_DATA to "GDAL No Data",
|
||||
).apply {
|
||||
putAll(DngTags.tagNameMap)
|
||||
putAll(ExifGeoTiffTags.tagNameMap)
|
||||
|
|
|
@ -9,16 +9,27 @@ object GeoTiffKeys {
|
|||
private const val CITATION = 0x0402
|
||||
private const val GEOG_TYPE = 0x0800
|
||||
private const val GEOG_CITATION = 0x0801
|
||||
private const val GEOG_GEODETIC_DATUM = 0x0802
|
||||
private const val GEOG_LINEAR_UNITS = 0x0804
|
||||
private const val GEOG_ANGULAR_UNITS = 0x0806
|
||||
private const val GEOG_ELLIPSOID = 0x0808
|
||||
private const val GEOG_SEMI_MAJOR_AXIS = 0x0809
|
||||
private const val GEOG_SEMI_MINOR_AXIS = 0x080a
|
||||
private const val GEOG_INV_FLATTENING = 0x080b
|
||||
private const val PROJ_CS_TYPE = 0x0c00
|
||||
private const val PROJ_CS_CITATION = 0x0c01
|
||||
private const val PROJECTION = 0x0c02
|
||||
private const val PROJ_COORD_TRANS = 0x0c03
|
||||
private const val PROJ_LINEAR_UNITS = 0x0c04
|
||||
private const val PROJ_STD_PARALLEL_1 = 0x0c06
|
||||
private const val PROJ_STD_PARALLEL_2 = 0x0c07
|
||||
private const val PROJ_NAT_ORIGIN_LONG = 0x0c08
|
||||
private const val PROJ_NAT_ORIGIN_LAT = 0x0c09
|
||||
private const val PROJ_FALSE_EASTING = 0x0c0a
|
||||
private const val PROJ_FALSE_NORTHING = 0x0c0b
|
||||
private const val PROJ_SCALE_AT_NAT_ORIGIN = 0x0c14
|
||||
private const val PROJ_AZIMUTH_ANGLE = 0x0c16
|
||||
private const val VERTICAL_UNITS = 0x1003
|
||||
|
||||
private val tagNameMap = hashMapOf(
|
||||
GEOTIFF_VERSION to "GeoTIFF Version",
|
||||
|
@ -27,16 +38,27 @@ object GeoTiffKeys {
|
|||
CITATION to "Citation",
|
||||
GEOG_TYPE to "Geographic Type",
|
||||
GEOG_CITATION to "Geographic Citation",
|
||||
GEOG_GEODETIC_DATUM to "Geographic Geodetic Datum",
|
||||
GEOG_LINEAR_UNITS to "Geographic Linear Units",
|
||||
GEOG_ANGULAR_UNITS to "Geographic Angular Units",
|
||||
GEOG_ELLIPSOID to "Geographic Ellipsoid",
|
||||
GEOG_SEMI_MAJOR_AXIS to "Semi-major axis",
|
||||
GEOG_SEMI_MINOR_AXIS to "Semi-minor axis",
|
||||
GEOG_INV_FLATTENING to "Inv. Flattening",
|
||||
PROJ_CS_TYPE to "Projected Coordinate System Type",
|
||||
PROJ_CS_CITATION to "Projected Coordinate System Citation",
|
||||
PROJECTION to "Projection",
|
||||
PROJ_COORD_TRANS to "Projected Coordinate Transform",
|
||||
PROJ_LINEAR_UNITS to "Projection Linear Units",
|
||||
PROJ_STD_PARALLEL_1 to "Projection Standard Parallel 1",
|
||||
PROJ_STD_PARALLEL_2 to "Projection Standard Parallel 2",
|
||||
PROJ_NAT_ORIGIN_LONG to "Projection Natural Origin Longitude",
|
||||
PROJ_NAT_ORIGIN_LAT to "Projection Natural Origin Latitude",
|
||||
PROJ_FALSE_EASTING to "Projection False Easting",
|
||||
PROJ_FALSE_NORTHING to "Projection False Northing",
|
||||
PROJ_SCALE_AT_NAT_ORIGIN to "Projection Scale at Natural Origin",
|
||||
PROJ_AZIMUTH_ANGLE to "Projection Azimuth Angle",
|
||||
VERTICAL_UNITS to "Vertical Units",
|
||||
)
|
||||
|
||||
fun getTagName(tag: Int): String? {
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"entryActionPrint": "Print",
|
||||
"entryActionShare": "Share",
|
||||
"entryActionViewSource": "View source",
|
||||
"entryActionShowGeoTiffOnMap": "Show as map overlay",
|
||||
"entryActionViewMotionPhotoVideo": "Open Motion Photo",
|
||||
"entryActionEdit": "Edit",
|
||||
"entryActionOpen": "Open with",
|
||||
|
|
|
@ -10,6 +10,8 @@ enum EntryInfoAction {
|
|||
editRating,
|
||||
editTags,
|
||||
removeMetadata,
|
||||
// GeoTIFF
|
||||
showGeoTiffOnMap,
|
||||
// motion photo
|
||||
viewMotionPhotoVideo,
|
||||
// debug
|
||||
|
@ -23,6 +25,7 @@ class EntryInfoActions {
|
|||
EntryInfoAction.editRating,
|
||||
EntryInfoAction.editTags,
|
||||
EntryInfoAction.removeMetadata,
|
||||
EntryInfoAction.showGeoTiffOnMap,
|
||||
EntryInfoAction.viewMotionPhotoVideo,
|
||||
];
|
||||
}
|
||||
|
@ -41,6 +44,9 @@ extension ExtraEntryInfoAction on EntryInfoAction {
|
|||
return context.l10n.entryInfoActionEditTags;
|
||||
case EntryInfoAction.removeMetadata:
|
||||
return context.l10n.entryInfoActionRemoveMetadata;
|
||||
// GeoTIFF
|
||||
case EntryInfoAction.showGeoTiffOnMap:
|
||||
return context.l10n.entryActionShowGeoTiffOnMap;
|
||||
// motion photo
|
||||
case EntryInfoAction.viewMotionPhotoVideo:
|
||||
return context.l10n.entryActionViewMotionPhotoVideo;
|
||||
|
@ -77,6 +83,9 @@ extension ExtraEntryInfoAction on EntryInfoAction {
|
|||
return AIcons.editTags;
|
||||
case EntryInfoAction.removeMetadata:
|
||||
return AIcons.clear;
|
||||
// GeoTIFF
|
||||
case EntryInfoAction.showGeoTiffOnMap:
|
||||
return AIcons.map;
|
||||
// motion photo
|
||||
case EntryInfoAction.viewMotionPhotoVideo:
|
||||
return AIcons.motionPhoto;
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'dart:ui';
|
|||
import 'package:aves/geo/countries.dart';
|
||||
import 'package:aves/model/entry_cache.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/metadata/address.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/metadata/trash.dart';
|
||||
|
@ -504,16 +505,35 @@ class AvesEntry {
|
|||
}
|
||||
catalogMetadata = CatalogMetadata(id: id);
|
||||
} else {
|
||||
// pre-processing
|
||||
if (isVideo && (!isSized || durationMillis == 0)) {
|
||||
// exotic video that is not sized during loading
|
||||
final fields = await VideoMetadataFormatter.getLoadingMetadata(this);
|
||||
await applyNewFields(fields, persist: persist);
|
||||
}
|
||||
|
||||
// cataloguing on platform
|
||||
catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background);
|
||||
|
||||
// post-processing
|
||||
if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) {
|
||||
catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this);
|
||||
}
|
||||
if (isGeotiff && !hasGps) {
|
||||
final info = await metadataFetchService.getGeoTiffInfo(this);
|
||||
if (info != null) {
|
||||
final center = MappedGeoTiff(
|
||||
info: info,
|
||||
entry: this,
|
||||
).center;
|
||||
if (center != null) {
|
||||
catalogMetadata = catalogMetadata?.copyWith(
|
||||
latitude: center.latitude,
|
||||
longitude: center.longitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
231
lib/model/geotiff.dart
Normal file
231
lib/model/geotiff.dart
Normal file
|
@ -0,0 +1,231 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_images.dart';
|
||||
import 'package:aves/ref/geotiff.dart';
|
||||
import 'package:aves/utils/geo_utils.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:aves/widgets/common/map/tile.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:proj4dart/proj4dart.dart' as proj4;
|
||||
|
||||
@immutable
|
||||
class GeoTiffInfo extends Equatable {
|
||||
final List<double>? modelPixelScale, modelTiePoints, modelTransformation;
|
||||
final int? projCSType, projLinearUnits;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [modelPixelScale, modelTiePoints, modelTransformation, projCSType, projLinearUnits];
|
||||
|
||||
const GeoTiffInfo({
|
||||
this.modelPixelScale,
|
||||
this.modelTiePoints,
|
||||
this.modelTransformation,
|
||||
this.projCSType,
|
||||
this.projLinearUnits,
|
||||
});
|
||||
|
||||
factory GeoTiffInfo.fromMap(Map map) {
|
||||
return GeoTiffInfo(
|
||||
modelPixelScale: (map[GeoTiffExifTags.modelPixelScale] as List?)?.cast<double>(),
|
||||
modelTiePoints: (map[GeoTiffExifTags.modelTiePoints] as List?)?.cast<double>(),
|
||||
modelTransformation: (map[GeoTiffExifTags.modelTransformation] as List?)?.cast<double>(),
|
||||
projCSType: map[GeoTiffKeys.projCSType] as int?,
|
||||
projLinearUnits: map[GeoTiffKeys.projLinearUnits] as int?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MappedGeoTiff {
|
||||
final AvesEntry entry;
|
||||
late LatLng? Function(Point<int> pixel) pointToLatLng;
|
||||
late Point<int>? Function(Point<double> smPoint) epsg3857ToPoint;
|
||||
|
||||
static final mapServiceTileSize = (256 * ui.window.devicePixelRatio).round();
|
||||
static final mapServiceHelper = MapServiceHelper(mapServiceTileSize);
|
||||
static final tileImagePaint = Paint();
|
||||
static final tileMissingPaint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = Colors.black;
|
||||
|
||||
MappedGeoTiff({
|
||||
required GeoTiffInfo info,
|
||||
required this.entry,
|
||||
}) {
|
||||
pointToLatLng = (_) => null;
|
||||
epsg3857ToPoint = (_) => null;
|
||||
|
||||
// limitation: only support some UTM coordinate systems
|
||||
final projCSType = info.projCSType;
|
||||
final srcProj4 = GeoUtils.epsgToProj4(projCSType);
|
||||
if (srcProj4 == null) {
|
||||
debugPrint('unsupported projCSType=$projCSType');
|
||||
return;
|
||||
}
|
||||
|
||||
// limitation: only support model space values in units of meters
|
||||
// TODO TLAD [geotiff] default from parsing proj4 instead of meter?
|
||||
final projLinearUnits = info.projLinearUnits ?? GeoTiffUnits.linearMeter;
|
||||
if (projLinearUnits != GeoTiffUnits.linearMeter) {
|
||||
debugPrint('unsupported projLinearUnits=$projLinearUnits');
|
||||
return;
|
||||
}
|
||||
|
||||
// limitation: only support tie points, not transformation matrix
|
||||
final modelTiePoints = info.modelTiePoints;
|
||||
if (modelTiePoints == null) return;
|
||||
|
||||
if (modelTiePoints.length < 6) return;
|
||||
|
||||
// map image space (I,J,K) to model space (X,Y,Z)
|
||||
final tpI = modelTiePoints[0];
|
||||
final tpJ = modelTiePoints[1];
|
||||
final tpK = modelTiePoints[2];
|
||||
final tpX = modelTiePoints[3];
|
||||
final tpY = modelTiePoints[4];
|
||||
final tpZ = modelTiePoints[5];
|
||||
|
||||
// limitation: expect 0,0,0,X,Y,0
|
||||
if (tpI != 0 || tpJ != 0 || tpK != 0 || tpZ != 0) return;
|
||||
|
||||
final modelPixelScale = info.modelPixelScale;
|
||||
if (modelPixelScale == null || modelPixelScale.length < 2) return;
|
||||
|
||||
final xScale = modelPixelScale[0];
|
||||
final yScale = modelPixelScale[1];
|
||||
|
||||
final geoTiffProjection = proj4.Projection.parse(srcProj4);
|
||||
final projToLatLng = proj4.ProjectionTuple(
|
||||
fromProj: geoTiffProjection,
|
||||
toProj: proj4.Projection.WGS84,
|
||||
);
|
||||
pointToLatLng = (pixel) {
|
||||
final srcPoint = proj4.Point(
|
||||
x: tpX + pixel.x * xScale,
|
||||
y: tpY - pixel.y * yScale,
|
||||
);
|
||||
final destPoint = projToLatLng.forward(srcPoint);
|
||||
|
||||
final latitude = destPoint.y;
|
||||
final longitude = destPoint.x;
|
||||
if (latitude >= -90.0 && latitude <= 90.0 && longitude >= -180.0 && longitude <= 180.0) {
|
||||
return LatLng(latitude, longitude);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
final projFromMapService = proj4.ProjectionTuple(
|
||||
fromProj: proj4.Projection.GOOGLE,
|
||||
toProj: geoTiffProjection,
|
||||
);
|
||||
epsg3857ToPoint = (smPoint) {
|
||||
final srcPoint = proj4.Point(x: smPoint.x, y: smPoint.y);
|
||||
final destPoint = projFromMapService.forward(srcPoint);
|
||||
return Point(((destPoint.x - tpX) / xScale).round(), -((destPoint.y - tpY) / yScale).round());
|
||||
};
|
||||
}
|
||||
|
||||
Future<MapTile?> getTile(int tx, int ty, int? zoomLevel) async {
|
||||
zoomLevel ??= 0;
|
||||
|
||||
// global projected coordinates in meters (EPSG:3857 Spherical Mercator)
|
||||
final tileTopLeft3857 = mapServiceHelper.tileTopLeft(tx, ty, zoomLevel);
|
||||
final tileBottomRight3857 = mapServiceHelper.tileTopLeft(tx + 1, ty + 1, zoomLevel);
|
||||
|
||||
// image region coordinates in pixels
|
||||
final tileTopLeftPx = epsg3857ToPoint(tileTopLeft3857);
|
||||
final tileBottomRightPx = epsg3857ToPoint(tileBottomRight3857);
|
||||
if (tileTopLeftPx == null || tileBottomRightPx == null) return null;
|
||||
|
||||
final tileLeft = tileTopLeftPx.x;
|
||||
final tileRight = tileBottomRightPx.x;
|
||||
final tileTop = tileTopLeftPx.y;
|
||||
final tileBottom = tileBottomRightPx.y;
|
||||
|
||||
final regionLeft = tileLeft.clamp(0, width);
|
||||
final regionRight = tileRight.clamp(0, width);
|
||||
final regionTop = tileTop.clamp(0, height);
|
||||
final regionBottom = tileBottom.clamp(0, height);
|
||||
|
||||
final regionWidth = regionRight - regionLeft;
|
||||
final regionHeight = regionBottom - regionTop;
|
||||
if (regionWidth == 0 || regionHeight == 0) return null;
|
||||
|
||||
final tileXScale = (tileRight - tileLeft) / mapServiceTileSize;
|
||||
final sampleSize = max<int>(1, highestPowerOf2(tileXScale));
|
||||
final region = entry.getRegion(
|
||||
sampleSize: sampleSize,
|
||||
region: Rectangle(regionLeft, regionTop, regionWidth, regionHeight),
|
||||
);
|
||||
|
||||
final imageInfoCompleter = Completer<ImageInfo?>();
|
||||
final imageStream = region.resolve(ImageConfiguration.empty);
|
||||
final imageStreamListener = ImageStreamListener((image, synchronousCall) {
|
||||
imageInfoCompleter.complete(image);
|
||||
}, onError: imageInfoCompleter.completeError);
|
||||
imageStream.addListener(imageStreamListener);
|
||||
ImageInfo? regionImageInfo;
|
||||
try {
|
||||
regionImageInfo = await imageInfoCompleter.future;
|
||||
} catch (error) {
|
||||
debugPrint('failed to get image for region=$region with error=$error');
|
||||
}
|
||||
imageStream.removeListener(imageStreamListener);
|
||||
|
||||
final imageOffset = Offset(
|
||||
regionLeft > tileLeft ? (regionLeft - tileLeft).toDouble() : 0,
|
||||
regionTop > tileTop ? (regionTop - tileTop).toDouble() : 0,
|
||||
);
|
||||
final tileImageScaleX = (tileRight - tileLeft) / mapServiceTileSize;
|
||||
final tileImageScaleY = (tileBottom - tileTop) / mapServiceTileSize;
|
||||
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.scale(1 / tileImageScaleX, 1 / tileImageScaleY);
|
||||
if (regionImageInfo != null) {
|
||||
final s = sampleSize.toDouble();
|
||||
canvas.scale(s, s);
|
||||
canvas.drawImage(regionImageInfo.image, imageOffset / s, tileImagePaint);
|
||||
canvas.scale(1 / s, 1 / s);
|
||||
} else {
|
||||
// fallback to show area
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
imageOffset.dx,
|
||||
imageOffset.dy,
|
||||
regionWidth.toDouble(),
|
||||
regionHeight.toDouble(),
|
||||
),
|
||||
tileMissingPaint,
|
||||
);
|
||||
}
|
||||
canvas.scale(tileImageScaleX, tileImageScaleY);
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
final tileImage = await picture.toImage(mapServiceTileSize, mapServiceTileSize);
|
||||
final byteData = await tileImage.toByteData(format: ui.ImageByteFormat.png);
|
||||
if (byteData == null) return null;
|
||||
|
||||
return MapTile(
|
||||
width: tileImage.width,
|
||||
height: tileImage.height,
|
||||
data: byteData.buffer.asUint8List(),
|
||||
);
|
||||
}
|
||||
|
||||
int get width => entry.width;
|
||||
|
||||
int get height => entry.height;
|
||||
|
||||
bool get canOverlay => center != null;
|
||||
|
||||
LatLng? get center => pointToLatLng(Point((width / 2).round(), (height / 2).round()));
|
||||
|
||||
LatLng? get topLeft => pointToLatLng(const Point(0, 0));
|
||||
|
||||
LatLng? get bottomRight => pointToLatLng(Point(width, height));
|
||||
}
|
|
@ -55,6 +55,8 @@ class CatalogMetadata {
|
|||
int? dateMillis,
|
||||
bool? isMultiPage,
|
||||
int? rotationDegrees,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
}) {
|
||||
return CatalogMetadata(
|
||||
id: id ?? this.id,
|
||||
|
@ -68,8 +70,8 @@ class CatalogMetadata {
|
|||
rotationDegrees: rotationDegrees ?? this.rotationDegrees,
|
||||
xmpSubjects: xmpSubjects,
|
||||
xmpTitleDescription: xmpTitleDescription,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
rating: rating,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,9 +11,19 @@ class GeoTiffKeys {
|
|||
static const int modelType = 0x0400;
|
||||
static const int rasterType = 0x0401;
|
||||
static const int geographicType = 0x0800;
|
||||
static const int geogGeodeticDatum = 0x0802;
|
||||
static const int geogAngularUnits = 0x0806;
|
||||
static const int geogEllipsoid = 0x0808;
|
||||
static const int projCSType = 0x0c00;
|
||||
static const int projection = 0x0c02;
|
||||
static const int projCoordinateTransform = 0x0c03;
|
||||
static const int projLinearUnits = 0x0c04;
|
||||
static const int verticalUnits = 0x1003;
|
||||
}
|
||||
|
||||
class GeoTiffUnits {
|
||||
static const int linearMeter = 9001;
|
||||
static const int linearFootUSSurvey = 9003;
|
||||
|
||||
double footUSSurveyToMeter(double input) => input * 1200 / 3937;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/metadata/fields.dart';
|
||||
import 'package:aves/model/metadata/overlay.dart';
|
||||
|
@ -19,6 +20,8 @@ abstract class MetadataFetchService {
|
|||
|
||||
Future<OverlayMetadata?> getOverlayMetadata(AvesEntry entry);
|
||||
|
||||
Future<GeoTiffInfo?> getGeoTiffInfo(AvesEntry entry);
|
||||
|
||||
Future<MultiPageInfo?> getMultiPageInfo(AvesEntry entry);
|
||||
|
||||
Future<PanoramaInfo?> getPanoramaInfo(AvesEntry entry);
|
||||
|
@ -117,6 +120,23 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GeoTiffInfo?> getGeoTiffInfo(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getGeoTiffInfo', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
}) as Map;
|
||||
return GeoTiffInfo.fromMap(result);
|
||||
} on PlatformException catch (e, stack) {
|
||||
if (!entry.isMissingAtPath) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MultiPageInfo?> getMultiPageInfo(AvesEntry entry) async {
|
||||
try {
|
||||
|
|
|
@ -24,6 +24,7 @@ class AIcons {
|
|||
static const IconData location = Icons.place_outlined;
|
||||
static const IconData locationUnlocated = Icons.location_off_outlined;
|
||||
static const IconData mainStorage = Icons.smartphone_outlined;
|
||||
static const IconData opacity = Icons.opacity;
|
||||
static const IconData privacy = MdiIcons.shieldAccountOutline;
|
||||
static const IconData rating = Icons.star_border_outlined;
|
||||
static const IconData ratingFull = Icons.star;
|
||||
|
|
|
@ -317,6 +317,11 @@ class Constants {
|
|||
license: 'Apache 2.0',
|
||||
sourceUrl: 'https://github.com/DavBfr/dart_pdf',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Proj4dart',
|
||||
license: 'MIT',
|
||||
sourceUrl: 'https://github.com/maRci002/proj4dart',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Stack Trace',
|
||||
license: 'BSD 3-Clause',
|
||||
|
|
|
@ -56,4 +56,98 @@ class GeoUtils {
|
|||
final east = ne.longitude;
|
||||
return (south <= lat && lat <= north) && (west <= east ? (west <= lng && lng <= east) : (west <= lng || lng <= east));
|
||||
}
|
||||
|
||||
// cf https://epsg.io/EPSGCODE.proj4
|
||||
// cf https://github.com/stevage/epsg
|
||||
// cf https://github.com/maRci002/proj4dart/blob/master/test/data/all_proj4_defs.dart
|
||||
static String? epsgToProj4(int? epsg) {
|
||||
// `32767` refers to user defined values
|
||||
if (epsg == null || epsg == 32767) return null;
|
||||
|
||||
if (3038 <= epsg && epsg <= 3051) {
|
||||
// ETRS89 / UTM (N-E)
|
||||
final zone = epsg - 3012;
|
||||
return '+proj=utm +zone=$zone +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs';
|
||||
} else if (26700 <= epsg && epsg <= 26799) {
|
||||
// US State Plane (NAD27): 267xx/320xx
|
||||
if (26703 <= epsg && epsg <= 26722) {
|
||||
final zone = epsg - 26700;
|
||||
return '+proj=utm +zone=$zone +datum=NAD27 +units=m +no_defs';
|
||||
}
|
||||
// NAD27 datum requires loading `nadgrids` for accurate transformation:
|
||||
// cf https://github.com/proj4js/proj4js/pull/363
|
||||
// cf https://github.com/maRci002/proj4dart/issues/8
|
||||
if (epsg == 26746) {
|
||||
// NAD27 / California zone VI
|
||||
return '+proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666 +lon_0=-116.25 +x_0=609601.2192024384 +y_0=0 +datum=NAD27 +units=us-ft +no_defs';
|
||||
} else if (epsg == 26771) {
|
||||
// NAD27 / Illinois East
|
||||
return '+proj=tmerc +lat_0=36.66666666666666 +lon_0=-88.33333333333333 +k=0.9999749999999999 +x_0=152400.3048006096 +y_0=0 +datum=NAD27 +units=us-ft +no_defs';
|
||||
}
|
||||
} else if ((26900 <= epsg && epsg <= 26999) || (32100 <= epsg && epsg <= 32199)) {
|
||||
// US State Plane (NAD83): 269xx/321xx
|
||||
if (epsg == 26966) {
|
||||
// NAD83 Georgia East
|
||||
return '+proj=tmerc +lat_0=30 +lon_0=-82.16666666666667 +k=0.9999 +x_0=200000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs';
|
||||
}
|
||||
} else if (32200 <= epsg && epsg <= 32299) {
|
||||
// WGS72 / UTM northern hemisphere: 322zz where zz is UTM zone number
|
||||
final zone = epsg - 32200;
|
||||
return '+proj=utm +zone=$zone +ellps=WGS72 +towgs84=0,0,4.5,0,0,0.554,0.2263 +units=m +no_defs';
|
||||
} else if (32300 <= epsg && epsg <= 32399) {
|
||||
// WGS72 / UTM southern hemisphere: 323zz where zz is UTM zone number
|
||||
final zone = epsg - 32300;
|
||||
return '+proj=utm +zone=$zone +south +ellps=WGS72 +towgs84=0,0,4.5,0,0,0.554,0.2263 +units=m +no_defs';
|
||||
} else if (32400 <= epsg && epsg <= 32460) {
|
||||
// WGS72BE / UTM northern hemisphere: 324zz where zz is UTM zone number
|
||||
final zone = epsg - 32400;
|
||||
return '+proj=utm +zone=$zone +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs';
|
||||
} else if (32500 <= epsg && epsg <= 32599) {
|
||||
// WGS72BE / UTM southern hemisphere: 325zz where zz is UTM zone number
|
||||
final zone = epsg - 32500;
|
||||
return '+proj=utm +zone=$zone +south +ellps=WGS72 +towgs84=0,0,1.9,0,0,0.814,-0.38 +units=m +no_defs';
|
||||
} else if (32600 <= epsg && epsg <= 32699) {
|
||||
// WGS84 / UTM northern hemisphere: 326zz where zz is UTM zone number
|
||||
final zone = epsg - 32600;
|
||||
return '+proj=utm +zone=$zone +datum=WGS84 +units=m +no_defs';
|
||||
} else if (32700 <= epsg && epsg <= 32799) {
|
||||
// WGS84 / UTM southern hemisphere: 327zz where zz is UTM zone number
|
||||
final zone = epsg - 32700;
|
||||
return '+proj=utm +zone=$zone +south +datum=WGS84 +units=m +no_defs';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// cf https://www.maptiler.com/google-maps-coordinates-tile-bounds-projection/
|
||||
class MapServiceHelper {
|
||||
final int tileSize;
|
||||
late final double initialResolution, originShift;
|
||||
|
||||
MapServiceHelper(this.tileSize) {
|
||||
initialResolution = 2 * pi * 6378137 / tileSize;
|
||||
originShift = 2 * pi * 6378137 / 2.0;
|
||||
}
|
||||
|
||||
int matrixSize(int zoomLevel) {
|
||||
return 1 << zoomLevel;
|
||||
}
|
||||
|
||||
Point<double> pixelsToMeters(double px, double py, int zoomLevel) {
|
||||
double res = resolution(zoomLevel);
|
||||
double mx = px * res - originShift;
|
||||
double my = -py * res + originShift;
|
||||
return Point(mx, my);
|
||||
}
|
||||
|
||||
double resolution(int zoomLevel) {
|
||||
return initialResolution / matrixSize(zoomLevel);
|
||||
}
|
||||
|
||||
Point<double> tileTopLeft(int tx, int ty, int zoomLevel) {
|
||||
final px = tx * tileSize;
|
||||
final py = ty * tileSize;
|
||||
return pixelsToMeters(px.toDouble(), py.toDouble(), zoomLevel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
|
@ -33,6 +34,8 @@ class GeoMap extends StatefulWidget {
|
|||
final LatLng? initialCenter;
|
||||
final ValueNotifier<bool> isAnimatingNotifier;
|
||||
final ValueNotifier<LatLng?>? dotLocationNotifier;
|
||||
final ValueNotifier<double>? overlayOpacityNotifier;
|
||||
final MappedGeoTiff? overlayEntry;
|
||||
final UserZoomChangeCallback? onUserZoomChange;
|
||||
final void Function(LatLng location)? onMapTap;
|
||||
final MarkerTapCallback? onMarkerTap;
|
||||
|
@ -49,6 +52,8 @@ class GeoMap extends StatefulWidget {
|
|||
this.initialCenter,
|
||||
required this.isAnimatingNotifier,
|
||||
this.dotLocationNotifier,
|
||||
this.overlayOpacityNotifier,
|
||||
this.overlayEntry,
|
||||
this.onUserZoomChange,
|
||||
this.onMapTap,
|
||||
this.onMarkerTap,
|
||||
|
@ -176,6 +181,8 @@ class _GeoMapState extends State<GeoMap> {
|
|||
markerClusterBuilder: _buildMarkerClusters,
|
||||
markerWidgetBuilder: _buildMarkerWidget,
|
||||
dotLocationNotifier: widget.dotLocationNotifier,
|
||||
overlayOpacityNotifier: widget.overlayOpacityNotifier,
|
||||
overlayEntry: widget.overlayEntry,
|
||||
onUserZoomChange: widget.onUserZoomChange,
|
||||
onMapTap: widget.onMapTap,
|
||||
onMarkerTap: _onMarkerTap,
|
||||
|
@ -199,6 +206,8 @@ class _GeoMapState extends State<GeoMap> {
|
|||
DotMarker.diameter + ImageMarker.outerBorderWidth * 2,
|
||||
DotMarker.diameter + ImageMarker.outerBorderWidth * 2,
|
||||
),
|
||||
overlayOpacityNotifier: widget.overlayOpacityNotifier,
|
||||
overlayEntry: widget.overlayEntry,
|
||||
onUserZoomChange: widget.onUserZoomChange,
|
||||
onMapTap: widget.onMapTap,
|
||||
onMarkerTap: _onMarkerTap,
|
||||
|
@ -262,12 +271,27 @@ class _GeoMapState extends State<GeoMap> {
|
|||
}
|
||||
|
||||
ZoomedBounds _initBounds() {
|
||||
ZoomedBounds? bounds;
|
||||
|
||||
final overlayEntry = widget.overlayEntry;
|
||||
if (overlayEntry != null) {
|
||||
final corner1 = overlayEntry.topLeft;
|
||||
final corner2 = overlayEntry.bottomRight;
|
||||
if (corner1 != null && corner2 != null) {
|
||||
bounds = ZoomedBounds.fromPoints(
|
||||
points: {corner1, corner2},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (bounds == null) {
|
||||
final initialCenter = widget.initialCenter;
|
||||
final points = initialCenter != null ? {initialCenter} : entries.map((v) => v.latLng!).toSet();
|
||||
final bounds = ZoomedBounds.fromPoints(
|
||||
bounds = ZoomedBounds.fromPoints(
|
||||
points: points.isNotEmpty ? points : {Constants.wonders[Random().nextInt(Constants.wonders.length)]},
|
||||
collocationZoom: settings.infoMapZoom,
|
||||
);
|
||||
}
|
||||
|
||||
return bounds.copyWith(
|
||||
zoom: max(bounds.zoom, minInitialZoom),
|
||||
);
|
||||
|
|
17
lib/widgets/common/map/google/geotiff_tile_provider.dart
Normal file
17
lib/widgets/common/map/google/geotiff_tile_provider.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
class GeoTiffTileProvider extends TileProvider {
|
||||
MappedGeoTiff overlayEntry;
|
||||
|
||||
GeoTiffTileProvider(this.overlayEntry);
|
||||
|
||||
@override
|
||||
Future<Tile> getTile(int x, int y, int? zoom) async {
|
||||
final tile = await overlayEntry.getTile(x, y, zoom);
|
||||
if (tile != null) {
|
||||
return Tile(tile.width, tile.height, tile.data);
|
||||
}
|
||||
return TileProvider.noTile;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/entry_images.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves/widgets/common/map/buttons.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:aves/widgets/common/map/controller.dart';
|
|||
import 'package:aves/widgets/common/map/decorator.dart';
|
||||
import 'package:aves/widgets/common/map/geo_entry.dart';
|
||||
import 'package:aves/widgets/common/map/geo_map.dart';
|
||||
import 'package:aves/widgets/common/map/google/geotiff_tile_provider.dart';
|
||||
import 'package:aves/widgets/common/map/google/marker_generator.dart';
|
||||
import 'package:aves/widgets/common/map/marker.dart';
|
||||
import 'package:aves/widgets/common/map/theme.dart';
|
||||
|
@ -27,6 +29,8 @@ class EntryGoogleMap extends StatefulWidget {
|
|||
final MarkerClusterBuilder markerClusterBuilder;
|
||||
final MarkerWidgetBuilder markerWidgetBuilder;
|
||||
final ValueNotifier<ll.LatLng?>? dotLocationNotifier;
|
||||
final ValueNotifier<double>? overlayOpacityNotifier;
|
||||
final MappedGeoTiff? overlayEntry;
|
||||
final UserZoomChangeCallback? onUserZoomChange;
|
||||
final void Function(ll.LatLng location)? onMapTap;
|
||||
final void Function(GeoEntry geoEntry)? onMarkerTap;
|
||||
|
@ -43,6 +47,8 @@ class EntryGoogleMap extends StatefulWidget {
|
|||
required this.markerClusterBuilder,
|
||||
required this.markerWidgetBuilder,
|
||||
required this.dotLocationNotifier,
|
||||
this.overlayOpacityNotifier,
|
||||
this.overlayEntry,
|
||||
this.onUserZoomChange,
|
||||
this.onMapTap,
|
||||
this.onMarkerTap,
|
||||
|
@ -169,9 +175,13 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
|||
});
|
||||
|
||||
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
|
||||
final overlayEntry = widget.overlayEntry;
|
||||
return ValueListenableBuilder<ll.LatLng?>(
|
||||
valueListenable: widget.dotLocationNotifier ?? ValueNotifier(null),
|
||||
builder: (context, dotLocation, child) {
|
||||
return ValueListenableBuilder<double>(
|
||||
valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1),
|
||||
builder: (context, overlayOpacity, child) {
|
||||
return GoogleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
bearing: -bounds.rotation,
|
||||
|
@ -214,6 +224,15 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
|||
zIndex: 1,
|
||||
)
|
||||
},
|
||||
// TODO TLAD [geotiff] may use ground overlay instead when this is fixed: https://github.com/flutter/flutter/issues/26479
|
||||
tileOverlays: {
|
||||
if (overlayEntry != null && overlayEntry.canOverlay)
|
||||
TileOverlay(
|
||||
tileOverlayId: TileOverlayId(overlayEntry.entry.uri),
|
||||
tileProvider: GeoTiffTileProvider(overlayEntry),
|
||||
transparency: 1 - overlayOpacity,
|
||||
),
|
||||
},
|
||||
onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing),
|
||||
onCameraIdle: _onIdle,
|
||||
onTap: (position) => widget.onMapTap?.call(_fromGoogleLatLng(position)),
|
||||
|
@ -222,6 +241,8 @@ class _EntryGoogleMapState extends State<EntryGoogleMap> with WidgetsBindingObse
|
|||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onIdle() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry_images.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
@ -30,6 +32,8 @@ class EntryLeafletMap extends StatefulWidget {
|
|||
final MarkerWidgetBuilder markerWidgetBuilder;
|
||||
final ValueNotifier<LatLng?>? dotLocationNotifier;
|
||||
final Size markerSize, dotMarkerSize;
|
||||
final ValueNotifier<double>? overlayOpacityNotifier;
|
||||
final MappedGeoTiff? overlayEntry;
|
||||
final UserZoomChangeCallback? onUserZoomChange;
|
||||
final void Function(LatLng location)? onMapTap;
|
||||
final void Function(GeoEntry geoEntry)? onMarkerTap;
|
||||
|
@ -48,6 +52,8 @@ class EntryLeafletMap extends StatefulWidget {
|
|||
required this.dotLocationNotifier,
|
||||
required this.markerSize,
|
||||
required this.dotMarkerSize,
|
||||
this.overlayOpacityNotifier,
|
||||
this.overlayEntry,
|
||||
this.onUserZoomChange,
|
||||
this.onMapTap,
|
||||
this.onMarkerTap,
|
||||
|
@ -174,6 +180,7 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
|
|||
],
|
||||
children: [
|
||||
_buildMapLayer(),
|
||||
if (widget.overlayEntry != null) _buildOverlayImageLayer(),
|
||||
MarkerLayerWidget(
|
||||
options: MarkerLayerOptions(
|
||||
markers: markers,
|
||||
|
@ -214,6 +221,32 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildOverlayImageLayer() {
|
||||
final overlayEntry = widget.overlayEntry;
|
||||
if (overlayEntry == null) return const SizedBox();
|
||||
|
||||
final corner1 = overlayEntry.topLeft;
|
||||
final corner2 = overlayEntry.bottomRight;
|
||||
if (corner1 == null || corner2 == null) return const SizedBox();
|
||||
|
||||
return ValueListenableBuilder<double>(
|
||||
valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1),
|
||||
builder: (context, overlayOpacity, child) {
|
||||
return OverlayImageLayerWidget(
|
||||
options: OverlayImageLayerOptions(
|
||||
overlayImages: [
|
||||
OverlayImage(
|
||||
bounds: LatLngBounds(corner1, corner2),
|
||||
imageProvider: overlayEntry.entry.uriImage,
|
||||
opacity: overlayOpacity,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onBoundsChange() => _debouncer(_onIdle);
|
||||
|
||||
void _onIdle() {
|
||||
|
|
15
lib/widgets/common/map/tile.dart
Normal file
15
lib/widgets/common/map/tile.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@immutable
|
||||
class MapTile {
|
||||
final int width, height;
|
||||
final Uint8List data;
|
||||
|
||||
const MapTile({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.data,
|
||||
});
|
||||
}
|
|
@ -28,13 +28,13 @@ class ThumbnailEntryOverlay extends StatelessWidget {
|
|||
const AnimatedImageIcon()
|
||||
else ...[
|
||||
if (entry.isRaw && context.select<GridThemeData, bool>((t) => t.showRaw)) const RawIcon(),
|
||||
if (entry.isGeotiff) const GeoTiffIcon(),
|
||||
if (entry.is360) const SphericalImageIcon(),
|
||||
],
|
||||
if (entry.isMultiPage) ...[
|
||||
if (entry.isMotionPhoto && context.select<GridThemeData, bool>((t) => t.showMotionPhoto)) const MotionPhotoIcon(),
|
||||
if (!entry.isMotionPhoto) MultiPageIcon(entry: entry),
|
||||
],
|
||||
if (entry.isGeotiff) const GeoTiffIcon(),
|
||||
if (entry.trashed && context.select<GridThemeData, bool>((t) => t.showTrash)) TrashIcon(trashDaysLeft: entry.trashDaysLeft),
|
||||
];
|
||||
if (children.isEmpty) return const SizedBox();
|
||||
|
|
|
@ -4,12 +4,14 @@ import 'package:aves/app_mode.dart';
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/coordinate.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
import 'package:aves/model/settings/enums/enums.dart';
|
||||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||
|
@ -36,11 +38,13 @@ class MapPage extends StatelessWidget {
|
|||
|
||||
final CollectionLens collection;
|
||||
final AvesEntry? initialEntry;
|
||||
final MappedGeoTiff? overlayEntry;
|
||||
|
||||
const MapPage({
|
||||
Key? key,
|
||||
required this.collection,
|
||||
this.initialEntry,
|
||||
this.overlayEntry,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -59,6 +63,7 @@ class MapPage extends StatelessWidget {
|
|||
child: _Content(
|
||||
collection: collection,
|
||||
initialEntry: initialEntry,
|
||||
overlayEntry: overlayEntry,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -70,11 +75,13 @@ class MapPage extends StatelessWidget {
|
|||
class _Content extends StatefulWidget {
|
||||
final CollectionLens collection;
|
||||
final AvesEntry? initialEntry;
|
||||
final MappedGeoTiff? overlayEntry;
|
||||
|
||||
const _Content({
|
||||
Key? key,
|
||||
required this.collection,
|
||||
this.initialEntry,
|
||||
this.overlayEntry,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -89,6 +96,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
final ValueNotifier<CollectionLens?> _regionCollectionNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<LatLng?> _dotLocationNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<AvesEntry?> _dotEntryNotifier = ValueNotifier(null), _infoEntryNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<double> _overlayOpacityNotifier = ValueNotifier(1);
|
||||
final Debouncer _infoDebouncer = Debouncer(delay: Durations.mapInfoDebounceDelay);
|
||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||
late AnimationController _overlayAnimationController;
|
||||
|
@ -205,6 +213,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
children: [
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 0),
|
||||
_buildOverlayController(),
|
||||
_buildScroller(),
|
||||
],
|
||||
),
|
||||
|
@ -224,9 +233,11 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
controller: _mapController,
|
||||
collectionListenable: openingCollection,
|
||||
entries: openingCollection.sortedEntries,
|
||||
initialCenter: widget.initialEntry?.latLng,
|
||||
initialCenter: widget.initialEntry?.latLng ?? widget.overlayEntry?.center,
|
||||
isAnimatingNotifier: _isPageAnimatingNotifier,
|
||||
dotLocationNotifier: _dotLocationNotifier,
|
||||
overlayOpacityNotifier: _overlayOpacityNotifier,
|
||||
overlayEntry: widget.overlayEntry,
|
||||
onMapTap: (_) => _toggleOverlay(),
|
||||
onMarkerTap: (averageLocation, markerEntry, getClusterEntries) async {
|
||||
final index = regionCollection?.sortedEntries.indexOf(markerEntry);
|
||||
|
@ -240,6 +251,32 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildOverlayController() {
|
||||
if (widget.overlayEntry == null) return const SizedBox();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _overlayOpacityNotifier,
|
||||
builder: (context, overlayOpacity, child) {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(AIcons.opacity),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _overlayOpacityNotifier.value,
|
||||
onChanged: (v) => _overlayOpacityNotifier.value = v,
|
||||
min: 0,
|
||||
max: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScroller() {
|
||||
return Stack(
|
||||
children: [
|
||||
|
|
|
@ -4,10 +4,13 @@ import 'package:aves/model/actions/entry_info_actions.dart';
|
|||
import 'package:aves/model/actions/events.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_metadata_edition.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/entry_editor.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
import 'package:aves/widgets/map/map_page.dart';
|
||||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||
import 'package:aves/widgets/viewer/debug/debug_page.dart';
|
||||
import 'package:aves/widgets/viewer/embedded/notifications.dart';
|
||||
|
@ -34,6 +37,9 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
case EntryInfoAction.editTags:
|
||||
case EntryInfoAction.removeMetadata:
|
||||
return true;
|
||||
// GeoTIFF
|
||||
case EntryInfoAction.showGeoTiffOnMap:
|
||||
return entry.isGeotiff;
|
||||
// motion photo
|
||||
case EntryInfoAction.viewMotionPhotoVideo:
|
||||
return entry.isMotionPhoto;
|
||||
|
@ -56,6 +62,9 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
return entry.canEditTags;
|
||||
case EntryInfoAction.removeMetadata:
|
||||
return entry.canRemoveMetadata;
|
||||
// GeoTIFF
|
||||
case EntryInfoAction.showGeoTiffOnMap:
|
||||
return true;
|
||||
// motion photo
|
||||
case EntryInfoAction.viewMotionPhotoVideo:
|
||||
return true;
|
||||
|
@ -84,6 +93,10 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
case EntryInfoAction.removeMetadata:
|
||||
await _removeMetadata(context);
|
||||
break;
|
||||
// GeoTIFF
|
||||
case EntryInfoAction.showGeoTiffOnMap:
|
||||
await _showGeoTiffOnMap(context);
|
||||
break;
|
||||
// motion photo
|
||||
case EntryInfoAction.viewMotionPhotoVideo:
|
||||
OpenEmbeddedDataNotification.motionPhotoVideo().dispatch(context);
|
||||
|
@ -135,6 +148,36 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
await edit(context, () => entry.removeMetadata(types));
|
||||
}
|
||||
|
||||
Future<void> _showGeoTiffOnMap(BuildContext context) async {
|
||||
final info = await metadataFetchService.getGeoTiffInfo(entry);
|
||||
if (info == null) return;
|
||||
|
||||
final mappedGeoTiff = MappedGeoTiff(
|
||||
info: info,
|
||||
entry: entry,
|
||||
);
|
||||
if (!mappedGeoTiff.canOverlay) return;
|
||||
|
||||
final baseCollection = collection;
|
||||
if (baseCollection == null) return;
|
||||
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: MapPage.routeName),
|
||||
builder: (context) {
|
||||
return MapPage(
|
||||
collection: baseCollection.copyWith(
|
||||
listenToSource: true,
|
||||
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).where((entry) => entry != this.entry).toList(),
|
||||
),
|
||||
overlayEntry: mappedGeoTiff,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _goToDebug(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/ref/geotiff.dart';
|
||||
|
||||
class GeoTiffDirectory {
|
||||
// TODO TLAD [geotiff] avoid string-based match
|
||||
static int? tagForName(String name) {
|
||||
switch (name) {
|
||||
case 'Model Type':
|
||||
|
@ -9,8 +10,12 @@ class GeoTiffDirectory {
|
|||
return GeoTiffKeys.rasterType;
|
||||
case 'Geographic Type':
|
||||
return GeoTiffKeys.geographicType;
|
||||
case 'Geographic Geodetic Datum':
|
||||
return GeoTiffKeys.geogGeodeticDatum;
|
||||
case 'Geographic Angular Units':
|
||||
return GeoTiffKeys.geogAngularUnits;
|
||||
case 'Geographic Ellipsoid':
|
||||
return GeoTiffKeys.geogEllipsoid;
|
||||
case 'Projected Coordinate System Type':
|
||||
return GeoTiffKeys.projCSType;
|
||||
case 'Projection':
|
||||
|
@ -19,6 +24,8 @@ class GeoTiffDirectory {
|
|||
return GeoTiffKeys.projCoordinateTransform;
|
||||
case 'Projection Linear Units':
|
||||
return GeoTiffKeys.projLinearUnits;
|
||||
case 'Vertical Units':
|
||||
return GeoTiffKeys.verticalUnits;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -32,6 +39,10 @@ class GeoTiffDirectory {
|
|||
return getRasterTypeDescription(v);
|
||||
case GeoTiffKeys.geographicType:
|
||||
return getGeographicTypeDescription(v);
|
||||
case GeoTiffKeys.geogGeodeticDatum:
|
||||
return getGeogGeodeticDatumDescription(v);
|
||||
case GeoTiffKeys.geogEllipsoid:
|
||||
return getGeogEllipsoidDescription(v);
|
||||
case GeoTiffKeys.projCSType:
|
||||
return getProjCSTypeDescription(v);
|
||||
case GeoTiffKeys.projection:
|
||||
|
@ -40,6 +51,7 @@ class GeoTiffDirectory {
|
|||
return getProjCoordinateTransformDescription(v);
|
||||
case GeoTiffKeys.projLinearUnits:
|
||||
case GeoTiffKeys.geogAngularUnits:
|
||||
case GeoTiffKeys.verticalUnits:
|
||||
return getGeoTiffUnitsDescription(v);
|
||||
default:
|
||||
return v;
|
||||
|
@ -439,6 +451,410 @@ class GeoTiffDirectory {
|
|||
}
|
||||
}
|
||||
|
||||
static String getGeogGeodeticDatumDescription(String valueString) {
|
||||
final value = int.tryParse(valueString);
|
||||
if (value == null) return valueString;
|
||||
switch (value) {
|
||||
case 6001:
|
||||
return 'Airy 1830';
|
||||
case 6002:
|
||||
return 'Airy Modified 1849';
|
||||
case 6003:
|
||||
return 'Australian National Spheroid';
|
||||
case 6004:
|
||||
return 'Bessel 1841';
|
||||
case 6005:
|
||||
return 'Bessel Modified';
|
||||
case 6006:
|
||||
return 'Bessel Namibia';
|
||||
case 6007:
|
||||
return 'Clarke 1858';
|
||||
case 6008:
|
||||
return 'Clarke 1866';
|
||||
case 6009:
|
||||
return 'Clarke 1866 Michigan';
|
||||
case 6010:
|
||||
return 'Clarke 1880 Benoit';
|
||||
case 6011:
|
||||
return 'Clarke 1880 IGN';
|
||||
case 6012:
|
||||
return 'Clarke 1880 RGS';
|
||||
case 6013:
|
||||
return 'Clarke 1880 Arc';
|
||||
case 6014:
|
||||
return 'Clarke 1880 SGA 1922';
|
||||
case 6015:
|
||||
return 'Everest 1830 1937 Adjustment';
|
||||
case 6016:
|
||||
return 'Everest 1830 1967 Definition';
|
||||
case 6017:
|
||||
return 'Everest 1830 1975 Definition';
|
||||
case 6018:
|
||||
return 'Everest 1830 Modified';
|
||||
case 6019:
|
||||
return 'GRS 1980';
|
||||
case 6020:
|
||||
return 'Helmert 1906';
|
||||
case 6021:
|
||||
return 'Indonesian National Spheroid';
|
||||
case 6022:
|
||||
return 'International 1924';
|
||||
case 6023:
|
||||
return 'International 1967';
|
||||
case 6024:
|
||||
return 'Krassowsky 1960';
|
||||
case 6025:
|
||||
return 'NWL9D';
|
||||
case 6026:
|
||||
return 'NWL10D';
|
||||
case 6027:
|
||||
return 'Plessis 1817';
|
||||
case 6028:
|
||||
return 'Struve 1860';
|
||||
case 6029:
|
||||
return 'War Office';
|
||||
case 6030:
|
||||
return 'WGS84';
|
||||
case 6031:
|
||||
return 'GEM10C';
|
||||
case 6032:
|
||||
return 'OSU86F';
|
||||
case 6033:
|
||||
return 'OSU91A';
|
||||
case 6034:
|
||||
return 'Clarke 1880';
|
||||
case 6035:
|
||||
return 'Sphere';
|
||||
case 6201:
|
||||
return 'Adindan';
|
||||
case 6202:
|
||||
return 'Australian Geodetic Datum 1966';
|
||||
case 6203:
|
||||
return 'Australian Geodetic Datum 1984';
|
||||
case 6204:
|
||||
return 'Ain el Abd 1970';
|
||||
case 6205:
|
||||
return 'Afgooye';
|
||||
case 6206:
|
||||
return 'Agadez';
|
||||
case 6207:
|
||||
return 'Lisbon';
|
||||
case 6208:
|
||||
return 'Aratu';
|
||||
case 6209:
|
||||
return 'Arc 1950';
|
||||
case 6210:
|
||||
return 'Arc 1960';
|
||||
case 6211:
|
||||
return 'Batavia';
|
||||
case 6212:
|
||||
return 'Barbados';
|
||||
case 6213:
|
||||
return 'Beduaram';
|
||||
case 6214:
|
||||
return 'Beijing 1954';
|
||||
case 6215:
|
||||
return 'Reseau National Belge 1950';
|
||||
case 6216:
|
||||
return 'Bermuda 1957';
|
||||
case 6217:
|
||||
return 'Bern 1898';
|
||||
case 6218:
|
||||
return 'Bogota';
|
||||
case 6219:
|
||||
return 'Bukit Rimpah';
|
||||
case 6220:
|
||||
return 'Camacupa';
|
||||
case 6221:
|
||||
return 'Campo Inchauspe';
|
||||
case 6222:
|
||||
return 'Cape';
|
||||
case 6223:
|
||||
return 'Carthage';
|
||||
case 6224:
|
||||
return 'Chua';
|
||||
case 6225:
|
||||
return 'Corrego Alegre';
|
||||
case 6226:
|
||||
return 'Cote d Ivoire';
|
||||
case 6227:
|
||||
return 'Deir ez Zor';
|
||||
case 6228:
|
||||
return 'Douala';
|
||||
case 6229:
|
||||
return 'Egypt 1907';
|
||||
case 6230:
|
||||
return 'European Datum 1950';
|
||||
case 6231:
|
||||
return 'European Datum 1987';
|
||||
case 6232:
|
||||
return 'Fahud';
|
||||
case 6233:
|
||||
return 'Gandajika 1970';
|
||||
case 6234:
|
||||
return 'Garoua';
|
||||
case 6235:
|
||||
return 'Guyane Francaise';
|
||||
case 6236:
|
||||
return 'Hu Tzu Shan';
|
||||
case 6237:
|
||||
return 'Hungarian Datum 1972';
|
||||
case 6238:
|
||||
return 'Indonesian Datum 1974';
|
||||
case 6239:
|
||||
return 'Indian 1954';
|
||||
case 6240:
|
||||
return 'Indian 1975';
|
||||
case 6241:
|
||||
return 'Jamaica 1875';
|
||||
case 6242:
|
||||
return 'Jamaica 1969';
|
||||
case 6243:
|
||||
return 'Kalianpur';
|
||||
case 6244:
|
||||
return 'Kandawala';
|
||||
case 6245:
|
||||
return 'Kertau';
|
||||
case 6246:
|
||||
return 'Kuwait Oil Company';
|
||||
case 6247:
|
||||
return 'La Canoa';
|
||||
case 6248:
|
||||
return 'Provisional S American Datum 1956';
|
||||
case 6249:
|
||||
return 'Lake';
|
||||
case 6250:
|
||||
return 'Leigon';
|
||||
case 6251:
|
||||
return 'Liberia 1964';
|
||||
case 6252:
|
||||
return 'Lome';
|
||||
case 6253:
|
||||
return 'Luzon 1911';
|
||||
case 6254:
|
||||
return 'Hito XVIII 1963';
|
||||
case 6255:
|
||||
return 'Herat North';
|
||||
case 6256:
|
||||
return 'Mahe 1971';
|
||||
case 6257:
|
||||
return 'Makassar';
|
||||
case 6258:
|
||||
return 'European Reference System 1989';
|
||||
case 6259:
|
||||
return 'Malongo 1987';
|
||||
case 6260:
|
||||
return 'Manoca';
|
||||
case 6261:
|
||||
return 'Merchich';
|
||||
case 6262:
|
||||
return 'Massawa';
|
||||
case 6263:
|
||||
return 'Minna';
|
||||
case 6264:
|
||||
return 'Mhast';
|
||||
case 6265:
|
||||
return 'Monte Mario';
|
||||
case 6266:
|
||||
return 'M poraloko';
|
||||
case 6267:
|
||||
return 'North American Datum 1927';
|
||||
case 6268:
|
||||
return 'NAD Michigan';
|
||||
case 6269:
|
||||
return 'North American Datum 1983';
|
||||
case 6270:
|
||||
return 'Nahrwan 1967';
|
||||
case 6271:
|
||||
return 'Naparima 1972';
|
||||
case 6272:
|
||||
return 'New Zealand Geodetic Datum 1949';
|
||||
case 6273:
|
||||
return 'NGO 1948';
|
||||
case 6274:
|
||||
return 'Datum 73';
|
||||
case 6275:
|
||||
return 'Nouvelle Triangulation Francaise';
|
||||
case 6276:
|
||||
return 'NSWC 9Z 2';
|
||||
case 6277:
|
||||
return 'OSGB 1936';
|
||||
case 6278:
|
||||
return 'OSGB 1970 SN';
|
||||
case 6279:
|
||||
return 'OS SN 1980';
|
||||
case 6280:
|
||||
return 'Padang 1884';
|
||||
case 6281:
|
||||
return 'Palestine 1923';
|
||||
case 6282:
|
||||
return 'Pointe Noire';
|
||||
case 6283:
|
||||
return 'Geocentric Datum of Australia 1994';
|
||||
case 6284:
|
||||
return 'Pulkovo 1942';
|
||||
case 6285:
|
||||
return 'Qatar';
|
||||
case 6286:
|
||||
return 'Qatar 1948';
|
||||
case 6287:
|
||||
return 'Qornoq';
|
||||
case 6288:
|
||||
return 'Loma Quintana';
|
||||
case 6289:
|
||||
return 'Amersfoort';
|
||||
case 6290:
|
||||
return 'RT38';
|
||||
case 6291:
|
||||
return 'South American Datum 1969';
|
||||
case 6292:
|
||||
return 'Sapper Hill 1943';
|
||||
case 6293:
|
||||
return 'Schwarzeck';
|
||||
case 6294:
|
||||
return 'Segora';
|
||||
case 6295:
|
||||
return 'Serindung';
|
||||
case 6296:
|
||||
return 'Sudan';
|
||||
case 6297:
|
||||
return 'Tananarive 1925';
|
||||
case 6298:
|
||||
return 'Timbalai 1948';
|
||||
case 6299:
|
||||
return 'TM65';
|
||||
case 6300:
|
||||
return 'TM75';
|
||||
case 6301:
|
||||
return 'Tokyo';
|
||||
case 6302:
|
||||
return 'Trinidad 1903';
|
||||
case 6303:
|
||||
return 'Trucial Coast 1948';
|
||||
case 6304:
|
||||
return 'Voirol 1875';
|
||||
case 6305:
|
||||
return 'Voirol Unifie 1960';
|
||||
case 6306:
|
||||
return 'Bern 1938';
|
||||
case 6307:
|
||||
return 'Nord Sahara 1959';
|
||||
case 6308:
|
||||
return 'Stockholm 1938';
|
||||
case 6309:
|
||||
return 'Yacare';
|
||||
case 6310:
|
||||
return 'Yoff';
|
||||
case 6311:
|
||||
return 'Zanderij';
|
||||
case 6312:
|
||||
return 'Militar Geographische Institut';
|
||||
case 6313:
|
||||
return 'Reseau National Belge 1972';
|
||||
case 6314:
|
||||
return 'Deutsche Hauptdreiecksnetz';
|
||||
case 6315:
|
||||
return 'Conakry 1905';
|
||||
case 6317:
|
||||
return 'Dealul Piscului 1970';
|
||||
case 6322:
|
||||
return 'WGS72';
|
||||
case 6324:
|
||||
return 'WGS72 Transit Broadcast Ephemeris';
|
||||
case 6326:
|
||||
return 'WGS84';
|
||||
case 6901:
|
||||
return 'Ancienne Triangulation Francaise';
|
||||
case 6902:
|
||||
return 'Nord de Guerre';
|
||||
case 32767:
|
||||
return 'User Defined';
|
||||
default:
|
||||
return 'Unknown ($value)';
|
||||
}
|
||||
}
|
||||
|
||||
static String getGeogEllipsoidDescription(String valueString) {
|
||||
final value = int.tryParse(valueString);
|
||||
if (value == null) return valueString;
|
||||
switch (value) {
|
||||
case 7001:
|
||||
return 'Airy 1830';
|
||||
case 7002:
|
||||
return 'Airy Modified 1849';
|
||||
case 7003:
|
||||
return 'Australian National Spheroid';
|
||||
case 7004:
|
||||
return 'Bessel 1841';
|
||||
case 7005:
|
||||
return 'Bessel Modified';
|
||||
case 7006:
|
||||
return 'Bessel Namibia';
|
||||
case 7007:
|
||||
return 'Clarke 1858';
|
||||
case 7008:
|
||||
return 'Clarke 1866';
|
||||
case 7009:
|
||||
return 'Clarke 1866 Michigan';
|
||||
case 7010:
|
||||
return 'Clarke 1880 Benoit';
|
||||
case 7011:
|
||||
return 'Clarke 1880 IGN';
|
||||
case 7012:
|
||||
return 'Clarke 1880 RGS';
|
||||
case 7013:
|
||||
return 'Clarke 1880 Arc';
|
||||
case 7014:
|
||||
return 'Clarke 1880 SGA 1922';
|
||||
case 7015:
|
||||
return 'Everest 1830 1937 Adjustment';
|
||||
case 7016:
|
||||
return 'Everest 1830 1967 Definition';
|
||||
case 7017:
|
||||
return 'Everest 1830 1975 Definition';
|
||||
case 7018:
|
||||
return 'Everest 1830 Modified';
|
||||
case 7019:
|
||||
return 'GRS 1980';
|
||||
case 7020:
|
||||
return 'Helmert 1906';
|
||||
case 7021:
|
||||
return 'Indonesian National Spheroid';
|
||||
case 7022:
|
||||
return 'International 1924';
|
||||
case 7023:
|
||||
return 'International 1967';
|
||||
case 7024:
|
||||
return 'Krassowsky 1940';
|
||||
case 7025:
|
||||
return 'NWL 9D';
|
||||
case 7026:
|
||||
return 'NWL 10D';
|
||||
case 7027:
|
||||
return 'Plessis 1817';
|
||||
case 7028:
|
||||
return 'Struve 1860';
|
||||
case 7029:
|
||||
return 'War Office';
|
||||
case 7030:
|
||||
return 'WGS 84';
|
||||
case 7031:
|
||||
return 'GEM 10C';
|
||||
case 7032:
|
||||
return 'OSU86F';
|
||||
case 7033:
|
||||
return 'OSU91A';
|
||||
case 7034:
|
||||
return 'Clarke 1880';
|
||||
case 7035:
|
||||
return 'Sphere';
|
||||
case 32767:
|
||||
return 'User Defined';
|
||||
default:
|
||||
return 'Unknown ($value)';
|
||||
}
|
||||
}
|
||||
|
||||
static String getProjCSTypeDescription(String valueString) {
|
||||
final value = int.tryParse(valueString);
|
||||
if (value == null) return valueString;
|
||||
|
@ -469,6 +885,8 @@ class GeoTiffDirectory {
|
|||
return 'RT90 2 5 gon W';
|
||||
case 2600:
|
||||
return 'Lietuvos Koordinoei Sistema 1994';
|
||||
case 3045:
|
||||
return 'ETRS89 UTM zone 33N';
|
||||
case 3053:
|
||||
return 'Hjorsey 1955 Lambert';
|
||||
case 3057:
|
||||
|
|
|
@ -841,7 +841,7 @@ packages:
|
|||
source: hosted
|
||||
version: "4.2.4"
|
||||
proj4dart:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: proj4dart
|
||||
url: "https://pub.dartlang.org"
|
||||
|
|
|
@ -64,6 +64,7 @@ dependencies:
|
|||
percent_indicator:
|
||||
permission_handler:
|
||||
printing:
|
||||
proj4dart:
|
||||
provider:
|
||||
screen_brightness:
|
||||
shared_preferences:
|
||||
|
|
|
@ -1,5 +1,34 @@
|
|||
{
|
||||
"de": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"entryActionShowGeoTiffOnMap"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"entryActionShowGeoTiffOnMap",
|
||||
"displayRefreshRatePreferHighest",
|
||||
"displayRefreshRatePreferLowest",
|
||||
"themeBrightnessLight",
|
||||
|
|
Loading…
Reference in a new issue