Java tutorial
package com.wheelermarine.android.publicAccesses; import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; import com.wheelermarine.android.publicAccesses.dbase.DBaseReader; import com.wheelermarine.android.publicAccesses.dbase.Record; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.nocrala.tools.gis.data.esri.shapefile.ShapeFileReader; import org.nocrala.tools.gis.data.esri.shapefile.exception.InvalidShapeFileException; import org.nocrala.tools.gis.data.esri.shapefile.header.ShapeFileHeader; import org.nocrala.tools.gis.data.esri.shapefile.shape.AbstractShape; import org.nocrala.tools.gis.data.esri.shapefile.shape.ShapeType; import org.nocrala.tools.gis.data.esri.shapefile.shape.shapes.PointShape; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * <p> * This class downloads updates from the MN DNR's data deli web site. It * first requests the quick download page which contains a link to the ZIP * archive on the FTP server. It then downloads and uncompresses the ZIP * archive while simultaneously loading the public access data from the * DBase database file and loading it into the SQLite database. * </p> * <p> * Copyright 2013 Steven Wheeler<br/> * Released under the GPLv3 license, see LICENSE file for details. * </p> */ public class Updater extends AsyncTask<URL, Integer, Integer> { private static final String TAG = "PublicAccesses.Updater"; private static final int timeout = 60; private static final String userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17"; private static final String entryName = "shor_waspt3.dbf"; private static final String shapeEntryName = "shor_waspt3.shp"; private Activity activity; private Context context; private PublicAccessAdapter adapter; private ProgressDialog progress; private Exception error; public Updater(Activity activity, Context context, PublicAccessAdapter adapter) { this.activity = activity; this.context = context; this.adapter = adapter; } /** * Convert a UTM location to a latitude and longitude location. * * @param north the northing value. * @param east the easting value. * @param zone the UTM zone. * @return a Location containing the latitude and longitude. */ public static Location fromUTM(double north, double east, double zone) { double d = 0.99960000000000004; double d1 = 6378137; double d2 = 0.0066943799999999998; double d4 = (1 - Math.sqrt(1 - d2)) / (1 + Math.sqrt(1 - d2)); double d3 = d2 / (1 - d2); double d12 = (north / d) / (d1 * (1 - d2 / 4 - (3 * d2 * d2) / 64 - (5 * Math.pow(d2, 3)) / 256)); double d14 = d12 + ((3 * d4) / 2 - (27 * Math.pow(d4, 3)) / 32) * Math.sin(2 * d12) + ((21 * d4 * d4) / 16 - (55 * Math.pow(d4, 4)) / 32) * Math.sin(4 * d12) + ((151 * Math.pow(d4, 3)) / 96) * Math.sin(6 * d12); double d5 = d1 / Math.sqrt(1 - d2 * Math.sin(d14) * Math.sin(d14)); double d6 = Math.tan(d14) * Math.tan(d14); double d7 = d3 * Math.cos(d14) * Math.cos(d14); double d8 = (d1 * (1 - d2)) / Math.pow(1 - d2 * Math.sin(d14) * Math.sin(d14), 1.5); double d9 = (east - 500000) / (d5 * d); double lat = (d14 - ((d5 * Math.tan(d14)) / d8) * (((d9 * d9) / 2 - (((5 + 3 * d6 + 10 * d7) - 4 * d7 * d7 - 9 * d3) * Math.pow(d9, 4)) / 24) + (((61 + 90 * d6 + 298 * d7 + 45 * d6 * d6) - 252 * d3 - 3 * d7 * d7) * Math.pow(d9, 6)) / 720)) * 180 / Math.PI; double lon = (((zone - 1) * 6 - 180) + 3) + (((d9 - ((1 + 2 * d6 + d7) * Math.pow(d9, 3)) / 6) + (((((5 - 2 * d7) + 28 * d6) - 3 * d7 * d7) + 8 * d3 + 24 * d6 * d6) * Math.pow(d9, 5)) / 120) / Math.cos(d14)) * 180 / Math.PI; Location loc = new Location("MNDNR"); loc.setLatitude(lat); loc.setLongitude(lon); return loc; } @Override protected void onPreExecute() { // Setup the progress dialog box. progress = new ProgressDialog(context); progress.setTitle("Updating public accesses..."); progress.setMessage("Please wait."); progress.setCancelable(false); progress.setIndeterminate(true); progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.show(); } @Override protected Integer doInBackground(URL... urls) { try { final DatabaseHelper db = new DatabaseHelper(context); SQLiteDatabase database = db.getWritableDatabase(); if (database == null) throw new IllegalStateException("Unable to open database!"); database.beginTransaction(); try { // Clear out the old data. database.delete(DatabaseHelper.PublicAccessEntry.TABLE_NAME, null, null); // Connect to the web server and locate the FTP download link. Log.v(TAG, "Finding update: " + urls[0]); activity.runOnUiThread(new Runnable() { @Override public void run() { progress.setMessage("Locating update..."); progress.setIndeterminate(true); } }); Document doc = Jsoup.connect(urls[0].toString()).timeout(timeout * 1000).userAgent(userAgent).get(); URL dataURL = null; for (Element element : doc.select("a")) { if (element.hasAttr("href") && element.attr("href").startsWith("ftp://ftp.dnr.state.mn.us")) { dataURL = new URL(element.attr("href")); } } // Make sure the download URL was fund. if (dataURL == null) throw new FileNotFoundException("Unable to locate data URL."); // Connect to the FTP server and download the update. Log.v(TAG, "Downloading update: " + dataURL); activity.runOnUiThread(new Runnable() { @Override public void run() { progress.setMessage("Downloading update..."); progress.setIndeterminate(true); } }); FTPClient ftp = new FTPClient(); try { ftp.setConnectTimeout(timeout * 1000); ftp.setDefaultTimeout(timeout * 1000); ftp.connect(dataURL.getHost()); ftp.enterLocalPassiveMode(); // After connection attempt, you should check the reply code // to verify success. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { ftp.disconnect(); throw new IOException("FTP server refused connection: " + ftp.getReplyString()); } // Login using the standard anonymous credentials. if (!ftp.login("anonymous", "anonymous")) { ftp.disconnect(); throw new IOException("FTP Error: " + ftp.getReplyString()); } Map<Integer, Location> locations = null; // Download the ZIP archive. Log.v(TAG, "Downloading: " + dataURL.getFile()); ftp.setFileType(FTP.BINARY_FILE_TYPE); InputStream in = ftp.retrieveFileStream(dataURL.getFile()); if (in == null) throw new FileNotFoundException(dataURL.getFile() + " was not found!"); try { ZipInputStream zin = new ZipInputStream(in); try { // Locate the .dbf entry in the ZIP archive. ZipEntry entry; while ((entry = zin.getNextEntry()) != null) { if (entry.getName().endsWith(entryName)) { readDBaseFile(zin, database); } else if (entry.getName().endsWith(shapeEntryName)) { locations = readShapeFile(zin); } } } finally { try { zin.close(); } catch (Exception e) { // Ignore this error. } } } finally { in.close(); } if (locations != null) { final int recordCount = locations.size(); activity.runOnUiThread(new Runnable() { @Override public void run() { progress.setIndeterminate(false); progress.setMessage("Updating locations..."); progress.setMax(recordCount); } }); int progress = 0; for (int recordNumber : locations.keySet()) { PublicAccess access = db.getPublicAccessByRecordNumber(recordNumber); Location loc = locations.get(recordNumber); access.setLatitude(loc.getLatitude()); access.setLongitude(loc.getLongitude()); db.updatePublicAccess(access); publishProgress(++progress); } } } finally { if (ftp.isConnected()) ftp.disconnect(); } database.setTransactionSuccessful(); return db.getPublicAccessesCount(); } finally { database.endTransaction(); } } catch (Exception e) { error = e; Log.e(TAG, "Error loading data: " + e.getLocalizedMessage(), e); return -1; } } private Map<Integer, Location> readShapeFile(ZipInputStream zin) throws IOException, InvalidShapeFileException { ShapeFileReader reader = new ShapeFileReader(zin); ShapeFileHeader header = reader.getHeader(); if (header.getShapeType() != ShapeType.POINT) throw new InvalidShapeFileException("Unable to read " + header.getShapeType() + " shape files."); Map<Integer, Location> locations = new HashMap<Integer, Location>(); AbstractShape s; while ((s = reader.next()) != null) { if (s.getShapeType() == ShapeType.POINT) { PointShape point = (PointShape) s; int recordNumber = point.getHeader().getRecordNumber(); Location location = fromUTM(point.getY(), point.getX(), 15); locations.put(recordNumber, location); } else { Log.d(TAG, "Unknown shape type: " + s.getShapeType()); } } return locations; } private void readDBaseFile(ZipInputStream zin, SQLiteDatabase database) throws IOException { // Begin parsing the DBase data. DBaseReader reader = new DBaseReader(zin); final int recordCount = reader.size(); activity.runOnUiThread(new Runnable() { @Override public void run() { progress.setIndeterminate(false); progress.setMax(recordCount); } }); Log.v(TAG, "DBase version: " + reader.getHeader().getSignature()); Log.v(TAG, "Last Update: " + reader.getHeader().getLastUpdate()); Log.v(TAG, "Record Count: " + reader.size()); // Insert the records into the local database. int progress = 0; for (Record access : reader) { String lake = (String) access.getValue("LAKENAME"); if (lake == null || lake.isEmpty()) lake = (String) access.getValue("LAKE_NAME"); if (lake == null || lake.isEmpty()) lake = (String) access.getValue("ALT_NAME"); if (lake == null || lake.isEmpty()) lake = String.valueOf(progress); ContentValues values = new ContentValues(); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_NAME, (String) access.getValue("FAC_NAME")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_LAUNCH, (String) access.getValue("LAUNCHTYPE")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_RAMP, (String) access.getValue("RAMPTYPE")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_RAMPS, (Double) access.getValue("NUMRAMPS")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_DOCKS, (Double) access.getValue("NUMDOCKS")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_DIRECTIONS, (String) access.getValue("DIRECTIONS")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_LAKE, lake); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_COUNTY, (String) access.getValue("COUNTYNAME")); values.put(DatabaseHelper.PublicAccessEntry.COLUMN_NAME_RECORD_NUMBER, progress + 1); database.insert(DatabaseHelper.PublicAccessEntry.TABLE_NAME, null, values); publishProgress(++progress); } } @Override protected void onProgressUpdate(Integer... values) { // Update the progress bar. progress.setProgress(values[0]); } @Override protected void onPostExecute(Integer integer) { // Must dismiss the progres dialog or it will cause an exception later. progress.dismiss(); // Update list view. adapter.refresh(); String message; if (error != null) { message = "Error loading data: " + error.getLocalizedMessage(); } else { message = "Loaded " + integer + " public accesses."; } Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } }