diff --git a/mapcache/build.gradle b/mapcache/build.gradle index 84c037e0..5c47542c 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -44,7 +44,7 @@ android { } dependencies { - compile "mil.nga.geopackage:geopackage-android:1.2.4" // comment out to build locally + compile "mil.nga.geopackage:geopackage-android:1.2.5" // comment out to build locally //compile project(':geopackage-sdk') // uncomment me to build locally compile 'com.google.android.gms:play-services:8.4.0' } diff --git a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageManagerFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageManagerFragment.java index 48f1291b..a87169a2 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageManagerFragment.java +++ b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageManagerFragment.java @@ -41,6 +41,7 @@ import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ImageView; +import android.widget.ListView; import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; @@ -54,8 +55,10 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import mil.nga.geopackage.BoundingBox; import mil.nga.geopackage.GeoPackage; @@ -66,6 +69,9 @@ import mil.nga.geopackage.core.contents.ContentsDao; import mil.nga.geopackage.core.srs.SpatialReferenceSystem; import mil.nga.geopackage.core.srs.SpatialReferenceSystemDao; +import mil.nga.geopackage.extension.Extensions; +import mil.nga.geopackage.extension.link.FeatureTileLink; +import mil.nga.geopackage.extension.link.FeatureTileTableLinker; import mil.nga.geopackage.factory.GeoPackageFactory; import mil.nga.geopackage.features.columns.GeometryColumns; import mil.nga.geopackage.features.columns.GeometryColumnsDao; @@ -304,7 +310,7 @@ public void update(boolean includeExternal) { databases = manager.internalDatabases(); // Disable any active external databases - for(String externalDatabase: manager.externalDatabaseSet()) { + for (String externalDatabase : manager.externalDatabaseSet()) { active.removeDatabase(externalDatabase, true); } } @@ -732,10 +738,10 @@ private void exportDatabaseOptionCheckPermissions(final String database) { * * @param granted true if permission was granted */ - public void exportDatabaseAfterPermission(boolean granted){ - if(granted) { + public void exportDatabaseAfterPermission(boolean granted) { + if (granted) { exportDatabaseOption(exportDatabaseName); - }else{ + } else { showDisabledExternalExportPermissionsDialog(); } } @@ -1281,10 +1287,12 @@ private void tableOptions(final GeoPackageTable table) { adapter.add(getString(R.string.geopackage_table_index_features_label)); adapter.add(getString(R.string.geopackage_table_create_feature_tiles_label)); adapter.add(getString(R.string.geopackage_table_add_feature_overlay_label)); + adapter.add(getString(R.string.geopackage_table_link_label)); break; case TILE: adapter.add(getString(R.string.geopackage_table_tiles_load_label)); + adapter.add(getString(R.string.geopackage_table_link_label)); break; case FEATURE_OVERLAY: @@ -1347,6 +1355,9 @@ public void onClick(DialogInterface dialog, int item) { case FEATURE: createFeatureTilesTableOption(table); break; + case TILE: + linkTableOption(table); + break; } break; case 5: @@ -1356,7 +1367,13 @@ public void onClick(DialogInterface dialog, int item) { break; } break; - + case 6: + switch (table.getType()) { + case FEATURE: + linkTableOption(table); + break; + } + break; default: } } @@ -2661,6 +2678,137 @@ public void onClick(DialogInterface dialog, int id) { dialog.show(); } + /** + * Link table option + * + * @param table + */ + private void linkTableOption(final GeoPackageTable table) { + + // Get a feature tile table linker + GeoPackageManager manager = GeoPackageFactory.getManager(getActivity()); + GeoPackage geoPackage = manager.open(table.getDatabase()); + FeatureTileTableLinker linker = new FeatureTileTableLinker(geoPackage); + + // Get the tables that can be linked and the currently linked tables + final List tables = new ArrayList<>(); + List linkedTables = null; + switch (table.getType()) { + case FEATURE: + tables.addAll(geoPackage.getTileTables()); + linkedTables = linker.queryForFeatureTable(table.getName()); + break; + case TILE: + tables.addAll(geoPackage.getFeatureTables()); + linkedTables = linker.queryForTileTable(table.getName()); + break; + default: + throw new GeoPackageException("Unexpected table type: " + table.getType()); + } + + // Close the GeoPackage + geoPackage.close(); + + // Build a set of currently linked tables + final Set linkedTableSet = new HashSet<>(); + for(FeatureTileLink link: linkedTables){ + switch(table.getType()) { + case FEATURE: + linkedTableSet.add(link.getTileTableName()); + break; + case TILE: + linkedTableSet.add(link.getFeatureTableName()); + break; + } + } + + // Maintain a copy of the linked tables before changes get made + final Set originalLinkedTableSet = new HashSet<>(); + originalLinkedTableSet.addAll(linkedTableSet); + + // Build the list adapter for selecting linked tables + TableLinkAdapter linkAdapter = new TableLinkAdapter(getActivity(), + R.layout.table_link_row, tables, linkedTableSet); + View tableLinkView = inflater.inflate(R.layout.table_link, null); + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity(), R.style.AppCompatAlertDialogStyle); + dialog.setView(tableLinkView); + TextView titleTextView = (TextView) tableLinkView.findViewById(R.id.tableLinkTitleTextView); + switch (table.getType()) { + case FEATURE: + titleTextView.setText(getString(R.string.geopackage_table_link_tiles_list_title)); + break; + case TILE: + titleTextView.setText(getString(R.string.geopackage_table_link_features_list_title)); + break; + } + ListView listView = (ListView) tableLinkView.findViewById(R.id.tableLinkListView); + listView.setAdapter(linkAdapter); + + dialog.setPositiveButton(getString(R.string.button_ok_label), + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + + // Determine which links exist that were unchecked + Set removedLinks = new HashSet<>(); + removedLinks.addAll(originalLinkedTableSet); + removedLinks.removeAll(linkedTableSet); + + // Determine which links were newly checked + Set newLinks = new HashSet<>(); + newLinks.addAll(linkedTableSet); + newLinks.removeAll(originalLinkedTableSet); + + // Check if we need to unlink or linke tables + if(!removedLinks.isEmpty() || !newLinks.isEmpty()){ + + // Create a linker + GeoPackageManager manager = GeoPackageFactory.getManager(getActivity()); + GeoPackage geoPackage = manager.open(table.getDatabase()); + FeatureTileTableLinker linker = new FeatureTileTableLinker(geoPackage); + + // Delete links + for(String removedLink: removedLinks){ + switch(table.getType()) { + case FEATURE: + linker.deleteLink(table.getName(), removedLink); + break; + case TILE: + linker.deleteLink(removedLink, table.getName()); + break; + } + } + + // Create links + for(String newLink: newLinks){ + switch(table.getType()) { + case FEATURE: + linker.link(table.getName(), newLink); + break; + case TILE: + linker.link(newLink, table.getName()); + break; + } + } + + // Close the GeoPackage and mark as changes made + geoPackage.close(); + active.setModified(true); + } + + } + }).setNegativeButton(getString(R.string.button_cancel_label), + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + dialog.show(); + + } /** * Add feature overlay table option @@ -3381,9 +3529,9 @@ public void onClick(DialogInterface dialog, int which) { * @param granted */ public void importGeoPackageExternalLinkAfterPermissionGranted(boolean granted) { - if(granted) { + if (granted) { importGeoPackageExternalLink(importExternalName, importExternalUri, importExternalPath); - }else{ + } else { showDisabledExternalImportPermissionsDialog(); } } @@ -3914,4 +4062,64 @@ public boolean isChildSelectable(int i, int j) { } + /** + * Table Link Adapter for displaying linkable tables + */ + private class TableLinkAdapter extends ArrayAdapter { + + /** + * Set of currently linked tables + */ + private final Set linkedTables; + + /** + * Constructor + * @param context + * @param textViewResourceId + * @param tables tables that can be linked + * @param linkedTables set of currently linked tables + */ + public TableLinkAdapter(Context context, int resource, + List tables, + Set linkedTables) { + super(context, resource, tables); + this.linkedTables = linkedTables; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + + if (view == null) { + view = inflater.inflate(R.layout.table_link_row, null); + } + + final String linkTable = getItem(position); + + CheckBox checkBox = (CheckBox) view + .findViewById(R.id.table_link_row_checkbox); + TextView tableName = (TextView) view + .findViewById(R.id.table_link_row_name); + + // Add or remove the table from being clicked on checkbox changes + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if(isChecked){ + linkedTables.add(linkTable); + }else{ + linkedTables.remove(linkTable); + } + } + }); + + // Set the initial values + checkBox.setChecked(linkedTables.contains(linkTable)); + tableName.setText(linkTable); + + return view; + } + + } + } diff --git a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java index 97481478..22aff72d 100755 --- a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java +++ b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java @@ -82,6 +82,8 @@ import mil.nga.geopackage.GeoPackageManager; import mil.nga.geopackage.core.contents.Contents; import mil.nga.geopackage.core.contents.ContentsDao; +import mil.nga.geopackage.extension.link.FeatureTileLink; +import mil.nga.geopackage.extension.link.FeatureTileTableLinker; import mil.nga.geopackage.factory.GeoPackageFactory; import mil.nga.geopackage.features.columns.GeometryColumns; import mil.nga.geopackage.features.index.FeatureIndexManager; @@ -110,6 +112,7 @@ import mil.nga.geopackage.tiles.features.MapFeatureTiles; import mil.nga.geopackage.tiles.features.custom.NumberFeaturesTile; import mil.nga.geopackage.tiles.matrixset.TileMatrixSet; +import mil.nga.geopackage.tiles.overlay.BoundedOverlay; import mil.nga.geopackage.tiles.overlay.FeatureOverlay; import mil.nga.geopackage.tiles.overlay.FeatureOverlayQuery; import mil.nga.geopackage.tiles.overlay.GeoPackageOverlayFactory; @@ -1010,7 +1013,7 @@ public void onHiddenChanged(boolean hidden) { visible = !hidden; // If my location did not have permissions to update and the map is becoming visible, ask for permission - if(!setMyLocationEnabled() && visible){ + if (!setMyLocationEnabled() && visible) { if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) { new AlertDialog.Builder(getActivity(), R.style.AppCompatAlertDialogStyle) .setTitle(R.string.location_access_rational_title) @@ -1039,11 +1042,12 @@ public void onClick(DialogInterface dialog, int which) { /** * Set the my location enabled state on the map if permission has been granted + * * @return true if updated, false if permission is required */ - public boolean setMyLocationEnabled(){ + public boolean setMyLocationEnabled() { boolean updated = false; - if(ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){ + if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { map.setMyLocationEnabled(visible); updated = true; } @@ -2150,13 +2154,45 @@ private void displayTiles(GeoPackageTileTable tiles) { TileDao tileDao = geoPackage.getTileDao(tiles.getName()); - TileProvider overlay = GeoPackageOverlayFactory - .getTileProvider(tileDao); + BoundedOverlay overlay = GeoPackageOverlayFactory + .getBoundedOverlay(tileDao); TileMatrixSet tileMatrixSet = tileDao.getTileMatrixSet(); Contents contents = tileMatrixSet.getContents(); - displayTiles(overlay, contents, -2, null); + FeatureTileTableLinker linker = new FeatureTileTableLinker(geoPackage); + List linkedFeatureTables = linker.queryForTileTable(tileDao.getTableName()); + for (FeatureTileLink link : linkedFeatureTables) { + + // Get a feature DAO and create the feature tiles + FeatureDao featureDao = geoPackage.getFeatureDao(link.getFeatureTableName()); + FeatureTiles featureTiles = new MapFeatureTiles(getActivity(), featureDao); + + // Create an index manager + FeatureIndexManager indexer = new FeatureIndexManager(getActivity(), geoPackage, featureDao); + featureTiles.setIndexManager(indexer); + + // Set the location and zoom bounds + overlay.setBoundingBox(tileDao.getBoundingBox(), tileDao.getProjection()); + overlay.setMinZoom((int) tileDao.getMinZoom()); + overlay.setMaxZoom((int) tileDao.getMaxZoom()); + + featureOverlayTiles = true; + + // Add the feature overlay query + FeatureOverlayQuery featureOverlayQuery = new FeatureOverlayQuery(getActivity(), overlay, featureTiles); + featureOverlayQueries.add(featureOverlayQuery); + } + + // Set the tiles index to be -2 of it is behind features and tiles drawn from features + int zIndex = -2; + + // If these tiles are linked to features, set the zIndex to -1 so they are placed before imagery tiles + if (!linkedFeatureTables.isEmpty()) { + zIndex = -1; + } + + displayTiles(overlay, contents, zIndex, null); } /** @@ -2666,18 +2702,18 @@ private boolean isWithinDistance(Projection projection, Point point, @Override public void onMapClick(LatLng point) { - if(!featureOverlayQueries.isEmpty()) { + if (!featureOverlayQueries.isEmpty()) { StringBuilder clickMessage = new StringBuilder(); for (FeatureOverlayQuery query : featureOverlayQueries) { String message = query.buildMapClickMessage(point, view, map); - if(message != null){ - if(clickMessage.length() > 0){ + if (message != null) { + if (clickMessage.length() > 0) { clickMessage.append("\n\n"); } clickMessage.append(message); } } - if(clickMessage.length() > 0){ + if (clickMessage.length() > 0) { new AlertDialog.Builder(getActivity(), R.style.AppCompatAlertDialogStyle) .setMessage(clickMessage.toString()) .setPositiveButton(android.R.string.yes, @@ -3874,14 +3910,15 @@ private void loadTilesFinished() { /** * Get the GeoPackage databases. If external storage permissions granted get all, if not get only internal + * * @return */ - private List getDatabases(){ + private List getDatabases() { List databases = null; if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { databases = manager.databases(); - }else{ + } else { databases = manager.internalDatabases(); } diff --git a/mapcache/src/main/res/layout/table_link.xml b/mapcache/src/main/res/layout/table_link.xml new file mode 100644 index 00000000..311108d8 --- /dev/null +++ b/mapcache/src/main/res/layout/table_link.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/mapcache/src/main/res/layout/table_link_row.xml b/mapcache/src/main/res/layout/table_link_row.xml new file mode 100644 index 00000000..efc7c380 --- /dev/null +++ b/mapcache/src/main/res/layout/table_link_row.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index 616cda3a..dea05585 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -144,6 +144,9 @@ Load Tiles Create Tiles Add Tile Overlay + Linked Tables + Linked Feature Tables + Linked Tile Tables Edit Tile Overlay Feature Index Index