001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.shapefile;
017:
018: import java.io.File;
019: import java.io.FileNotFoundException;
020: import java.io.IOException;
021: import java.io.UnsupportedEncodingException;
022: import java.nio.charset.Charset;
023: import java.net.MalformedURLException;
024: import java.net.URI;
025: import java.net.URL;
026: import java.net.URLDecoder;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.Map;
030: import java.util.logging.Logger;
031:
032: import org.geotools.data.DataSourceException;
033: import org.geotools.data.DataStore;
034: import org.geotools.data.FileDataStoreFactorySpi;
035: import org.geotools.data.DataStoreFactorySpi.Param;
036: import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
037:
038: import com.vividsolutions.jts.geom.Geometry;
039:
040: /**
041: * Implementation of the DataStore service provider interface for Shapefiles.
042: * <p>
043: * The specific implementation of ShapefileDataStore created by this class is not specified. For
044: * more information on the connection parameters please review the following public Param constants.
045: * <ul>
046: * <li>{@link URLP}
047: * <li>{@link NAMESPACEP}
048: * <li>{@link CREATE_SPATIAL_INDEX}
049: * <li>{@link MEMORY_MAPPED}
050: * <li>{@link DBFCHARSET}
051: * </ul>
052: *
053: * @author Chris Holmes, TOPP
054: * @source $URL:
055: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/ShapefileDataStoreFactory.java $
056: * @version $Id: ShapefileDataStoreFactory.java 29501 2008-02-28 02:36:47Z jgarnett $
057: */
058: public class ShapefileDataStoreFactory implements
059: FileDataStoreFactorySpi {
060:
061: protected static final Logger LOGGER = org.geotools.util.logging.Logging
062: .getLogger("org.geotools.data.shapefile");
063: /**
064: * url to the .shp file.
065: */
066: public static final Param URLP = new Param("url", URL.class,
067: "url to a .shp file");
068:
069: /**
070: * Optional - uri of the FeatureType's namespace
071: */
072: public static final Param NAMESPACEP = new Param("namespace",
073: URI.class, "uri to a the namespace", false); //not required
074:
075: /**
076: * Optional - enable/disable the use of memory-mapped io
077: */
078: public static final Param MEMORY_MAPPED = new Param(
079: "memory mapped buffer", Boolean.class,
080: "enable/disable the use of memory-mapped io", false);
081:
082: /**
083: * Optional - Enable/disable the automatic creation of spatial index
084: */
085: public static final Param CREATE_SPATIAL_INDEX = new Param(
086: "create spatial index", Boolean.class,
087: "enable/disable the automatic creation of spatial index",
088: false);
089:
090: /**
091: * Optional - character used to decode strings from the DBF file
092: */
093: public static final Param DBFCHARSET = new Param("charset",
094: Charset.class,
095: "character used to decode strings from the DBF file",
096: false, Charset.forName("ISO-8859-1")) {
097: /* This is an example of a non simple Param type where a custom parse
098: * method is required.
099: * @see org.geotools.data.DataStoreFactorySpi.Param#parse(java.lang.String)
100: */
101: public Object parse(String text) throws IOException {
102: return Charset.forName(text);
103: }
104:
105: public String text(Object value) {
106: return ((Charset) value).name();
107: }
108: };
109:
110: /**
111: * It is the users responsibility to prevent the creation of duplicate DataStore instances
112: * (they should hold the results in a Map, Repository or Catalog as described in the documenation).
113: * <p>
114: * However in the real world we are patching the problem, this Map is used to store
115: * previously created DataStores so we can hand them out again. There are two problems with
116: * this approach:
117: * <ul>
118: * <li>The ShapefileDataStoreFactory may be created more than once with different Hints, for example when
119: * used to read a shapefile into alternate Feature or Geometry representations.
120: * <li>It flys in the face of a DataStore lifecycle, the JDBC DataStore implementations
121: * have taken to support a dispose operation.
122: * </ul>
123: * As such I expect this "feature" to disappear, I am keeping it now only so we can
124: * produce good warnings.
125: */
126: private static Map liveStores = Collections
127: .synchronizedMap(new HashMap());
128:
129: /**
130: * Takes a map of parameters which describes how to access a DataStore
131: * and determines if it can be read by the ShapefileDataStore or IndexedShapefileDataStore
132: * implementations.
133: *
134: * @param params A map of parameters describing the location of a datastore.
135: * Files should be pointed to by a 'url' param.
136: *
137: * @return true iff params contains a url param which points to a file
138: * ending in shp
139: */
140: public boolean canProcess(Map params) {
141: boolean accept = false;
142: if (params.containsKey(URLP.key)) {
143: try {
144: URL url = (URL) URLP.lookUp(params);
145: accept = canProcess(url);
146: } catch (IOException ioe) {
147: // yes, I am eating this - since it is my job to return a true/false
148: }
149: }
150: return accept;
151: }
152:
153: /**
154: * Returns an instance of DataStore iff the resource pointed to the
155: * Map of paramers can be handled as a shapefile.
156: * <p>
157: * The specific implementation of ShapefileDataStore returned is not specified,
158: * and depends on the parameters given. For more information please review the
159: * public static Param instances available for this class.
160: * </p>
161: * <ul>
162: * <li>{@link URLP}
163: * <li>{@link NAMESPACEP}
164: * <li>{@link CREATE_SPATIAL_INDEX}
165: * <li>{@link MEMORY_MAPPED}
166: * <li>{@link DBFCHARSET}
167: * </ul>
168: * @param params A param list with information on the location of a
169: * restore. For shapefiles this should contain a 'url' param which
170: * points to a file which ends in shp.
171: *
172: * @return DataStore A ShapefileDatastore
173: * @throws IOException If a connection error (such as the file not existing occurs)
174: * @throws DataSourceException Thrown if the datastore which is created
175: * cannot be attached to the restore specified in params.
176: */
177: public DataStore createDataStore(Map params) throws IOException {
178: DataStore ds = null;
179: synchronized (liveStores) {
180: if (!liveStores.containsKey(params)) {
181: URL url = null;
182: try {
183: ds = createDataStoreInstance(params);
184: liveStores.put(params, ds);
185: } catch (MalformedURLException mue) {
186: throw new DataSourceException(
187: "Unable to attatch datastore to " + url,
188: mue);
189: }
190: } else {
191: ds = (DataStore) liveStores.get(params);
192: }
193: }
194: return ds;
195: }
196:
197: /**
198: * Creates a new DataStore - for a file that does not exist yet.
199: * <p>
200: * This method has different logic than createDataStore. It is willing
201: * to be memory mapped, and generate an index for a local file that
202: * does not exist yet.
203: *
204: */
205: public DataStore createNewDataStore(Map params) throws IOException {
206: DataStore ds = null;
207: synchronized (liveStores) {
208: if (!liveStores.containsKey(params)) {
209: URL url = null;
210: try {
211: ds = createNewShapefile(params);
212: liveStores.put(params, ds);
213: } catch (MalformedURLException mue) {
214: throw new DataSourceException(
215: "Unable to attatch datastore to " + url,
216: mue);
217: }
218: } else {
219: ds = (DataStore) liveStores.get(params);
220: }
221: }
222: return ds;
223: }
224:
225: /**
226: * Will create a new shapefile baed on the provided parameters.
227: *
228: * @param params Map of parameters
229: * @throws IOException If the filename is not valid.
230: * @throws UnsupportedOperationException
231: */
232: DataStore createNewShapefile(Map params) throws IOException {
233: URL url = (URL) URLP.lookUp(params);
234: Boolean isMemoryMapped = (Boolean) MEMORY_MAPPED.lookUp(params);
235: URI namespace = (URI) NAMESPACEP.lookUp(params);
236: Charset dbfCharset = (Charset) DBFCHARSET.lookUp(params);
237: Boolean isCreateSpatialIndex = (Boolean) CREATE_SPATIAL_INDEX
238: .lookUp(params);
239:
240: if (isCreateSpatialIndex == null) {
241: // should not be needed as default is TRUE
242: assert (true);
243: isCreateSpatialIndex = Boolean.TRUE;
244: }
245: if (dbfCharset == null) {
246: assert (true);
247: // this should not happen as Charset.forName("ISO-8859-1") was used as the param default?
248: dbfCharset = Charset.forName("ISO-8859-1");
249: }
250: if (isMemoryMapped == null) {
251: assert (true);
252: // this should not happen as false was the default
253: isMemoryMapped = Boolean.FALSE;
254: }
255: URL shpUrl = toShpURL(url);
256:
257: boolean isLocal = shpUrl.getProtocol().equalsIgnoreCase("file");
258: if (!isLocal || new File(shpUrl.getFile()).exists()) {
259: LOGGER.warning("File already exists: " + shpUrl);
260: }
261: boolean useMemoryMappedBuffer = isLocal
262: && isMemoryMapped.booleanValue();
263: boolean createIndex = isCreateSpatialIndex.booleanValue()
264: && isLocal;
265:
266: try {
267: if (createIndex) {
268: return new IndexedShapefileDataStore(url, namespace,
269: useMemoryMappedBuffer, true,
270: IndexedShapefileDataStore.TREE_QIX, dbfCharset);
271: } else {
272: return new ShapefileDataStore(url, namespace,
273: useMemoryMappedBuffer, dbfCharset);
274: }
275: } catch (MalformedURLException mue) {
276: throw new DataSourceException(
277: "Url for shapefile malformed: " + url, mue);
278: }
279: }
280:
281: /**
282: * Will create the correct implementation of ShapefileDataStore for the provided
283: * parameters.
284: *
285: * @param params Map of parameters
286: * @throws IOException If the specified shapefle could not be accessed
287: * @throws UnsupportedOperationException
288: */
289: DataStore createDataStoreInstance(Map params) throws IOException {
290: URL url = (URL) URLP.lookUp(params);
291: Boolean isMemoryMapped = (Boolean) MEMORY_MAPPED.lookUp(params);
292: URI namespace = (URI) NAMESPACEP.lookUp(params);
293: Charset dbfCharset = (Charset) DBFCHARSET.lookUp(params);
294: Boolean isCreateSpatialIndex = (Boolean) CREATE_SPATIAL_INDEX
295: .lookUp(params);
296:
297: if (isCreateSpatialIndex == null) {
298: // should not be needed as default is TRUE
299: isCreateSpatialIndex = Boolean.TRUE;
300: }
301: if (dbfCharset == null) {
302: // this should not happen as Charset.forName("ISO-8859-1") was used as the param default?
303: dbfCharset = Charset.forName("ISO-8859-1");
304: }
305: if (isMemoryMapped == null) {
306: isMemoryMapped = Boolean.FALSE;
307: }
308:
309: URL shpUrl = toShpURL(url);
310: boolean isLocal = shpUrl.getProtocol().equalsIgnoreCase("file");
311: if (isLocal && !new File(shpUrl.getFile()).exists()) {
312: throw new FileNotFoundException("Shapefile not found:"
313: + shpUrl.getFile());
314: }
315: boolean useMemoryMappedBuffer = isLocal
316: && new File(shpUrl.getFile()).exists()
317: && isMemoryMapped.booleanValue();
318: boolean createIndex = isCreateSpatialIndex.booleanValue()
319: && isLocal;
320: byte treeIndex = IndexedShapefileDataStore.TREE_NONE;
321: if (isLocal) {
322: if (createIndex) {
323: treeIndex = IndexedShapefileDataStore.TREE_QIX; // default
324: } else {
325: // lets check and see if any index file is avaialble
326: if (new File(toQixURL(url).getFile()).exists()) {
327: treeIndex = IndexedShapefileDataStore.TREE_QIX;
328: } else if (new File(toFixURL(url).getFile()).exists()) {
329: treeIndex = IndexedShapefileDataStore.TREE_GRX;
330: }
331: }
332: }
333:
334: try {
335: if (createIndex) {
336: return new IndexedShapefileDataStore(url, namespace,
337: useMemoryMappedBuffer, createIndex,
338: IndexedShapefileDataStore.TREE_QIX, dbfCharset);
339: } else if (treeIndex != IndexedShapefileDataStore.TREE_NONE) {
340: return new IndexedShapefileDataStore(url, namespace,
341: useMemoryMappedBuffer, false, treeIndex,
342: dbfCharset);
343: } else {
344: return new ShapefileDataStore(url, namespace,
345: useMemoryMappedBuffer, dbfCharset);
346: }
347: } catch (MalformedURLException mue) {
348: throw new DataSourceException(
349: "Url for shapefile malformed: " + url, mue);
350: }
351: }
352:
353: public String getDisplayName() {
354: return "Shapefile";
355: }
356:
357: /**
358: * Describes the type of data the datastore returned by this factory works
359: * with.
360: *
361: * @return String a human readable description of the type of restore
362: * supported by this datastore.
363: */
364: public String getDescription() {
365: return "ESRI(tm) Shapefiles (*.shp)";
366: }
367:
368: // public DataSourceMetadataEnity createMetadata( Map params )
369: // throws IOException {
370: //
371: // URL url = (URL) URLP.lookUp(params);
372: // Boolean mm = (Boolean) MEMORY_MAPPED.lookUp(params);
373: //
374: // String server;
375: // String name;
376: // if( url.getProtocol().equals("file")){
377: // server = "localhost";
378: // name = url.getPath();
379: // }
380: // else {
381: // server = url.getHost()+":"+url.getPort();
382: // name = url.getFile();
383: // }
384: // return new DataSourceMetadataEnity( server, name, "Shapefile access for "+url );
385: // }
386: /**
387: * Test to see if this datastore is available, if it has all the
388: * appropriate libraries to construct a datastore.
389: *
390: * This datastore just checks for the ShapefileDataStore, IndexedShapefileDataStore
391: * and Geometry implementations.
392: *
393: * @return <tt>true</tt> if and only if this factory is available to create
394: * DataStores.
395: */
396: public boolean isAvailable() {
397: try {
398: ShapefileDataStore.class.getName();
399: IndexedShapefileDataStore.class.getName();
400: Geometry.class.getName();
401: } catch (Exception e) {
402: return false;
403: }
404:
405: return true;
406: }
407:
408: /**
409: * Describe parameters.
410: *
411: *
412: * @see org.geotools.data.DataStoreFactorySpi#getParametersInfo()
413: */
414: public Param[] getParametersInfo() {
415: return new Param[] { URLP, NAMESPACEP, CREATE_SPATIAL_INDEX,
416: DBFCHARSET, MEMORY_MAPPED };
417: }
418:
419: /**
420: * @see org.geotools.data.dir.FileDataStoreFactorySpi#getFileExtensions()
421: */
422: public String[] getFileExtensions() {
423: return new String[] { ".shp", };
424: }
425:
426: /**
427: * @see org.geotools.data.dir.FileDataStoreFactorySpi#canProcess(java.net.URL)
428: */
429: public boolean canProcess(URL f) {
430: return f.getFile().toUpperCase().endsWith("SHP");
431: }
432:
433: /**
434: * We may need to create a new datastore if the provided file does not exist.
435: *
436: * @see org.geotools.data.dir.FileDataStoreFactorySpi#createDataStore(java.net.URL)
437: */
438: public DataStore createDataStore(URL url) throws IOException {
439: Map params = new HashMap();
440: params.put(URLP.key, url);
441:
442: boolean isLocal = url.getProtocol().equalsIgnoreCase("file");
443: if (isLocal && !(new File(url.getFile()).exists())) {
444: return createNewDataStore(params);
445: } else {
446: return createDataStore(params);
447: }
448: }
449:
450: /**
451: * @see org.geotools.data.dir.FileDataStoreFactorySpi#createDataStore(java.net.URL)
452: */
453: public DataStore createDataStore(URL url, boolean memorymapped)
454: throws IOException {
455: Map params = new HashMap();
456: params.put(URLP.key, url);
457: params.put(MEMORY_MAPPED.key, new Boolean(memorymapped));
458: return createDataStore(params);
459: }
460:
461: /**
462: * @see org.geotools.data.dir.FileDataStoreFactorySpi#getTypeName(java.net.URL)
463: */
464: public String getTypeName(URL url) throws IOException {
465: DataStore ds = createDataStore(url);
466: String[] names = ds.getTypeNames(); // should be exactly one
467: return ((names == null || names.length == 0) ? null : names[0]);
468: }
469:
470: /**
471: * Returns the implementation hints. The default implementation returns an
472: * empty map.
473: * <p>
474: * When we have FeatureFactory, GeometryFactory and so on hooked up this
475: * map will return Hints we paid attention too when we were constructed.
476: *
477: * @return An empty map.
478: */
479: public Map getImplementationHints() {
480: return Collections.EMPTY_MAP;
481: }
482:
483: /**
484: * Convert a URL to a String that is suitable for manipulation of its
485: * extension (generally the last three characters of the file).
486: *
487: * This uses URL.toExternalForm() in order to preserve valuable
488: * information about the URL's Authority.
489: *
490: * @param url the url to convert to a String. Must not be null.
491: * @return a String representation of the URL
492: * @throws MalformedURLException if the url is invalid
493: */
494: public static String toFilename(URL url)
495: throws MalformedURLException {
496: if (url == null) {
497: throw new NullPointerException(
498: "A shapefile URL is required");
499: }
500: try {
501: /*
502: * The use of the four parameter URL constructor is discouraged
503: * as it throws away valuable information about the authority that
504: * the URL is hosted by. It is better if we use the full URL
505: * String to reconstruct the other URLs.
506: */
507: return java.net.URLDecoder.decode(url.toExternalForm(),
508: "US-ASCII");
509: } catch (java.io.UnsupportedEncodingException use) {
510: throw new java.net.MalformedURLException(
511: "Unable to decode " + url + " cause "
512: + use.getMessage());
513: }
514: }
515:
516: /** Figure out the URL for the "fix" file */
517: public static URL toFixURL(URL url)
518: throws java.net.MalformedURLException {
519: String filename = toFilename(url);
520: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
521: || filename.endsWith(".shx")) {
522: filename = filename.substring(0, filename.length() - 4);
523: filename += ".fix";
524: } else if (filename.endsWith(".SHP")
525: || filename.endsWith(".DBF")
526: || filename.endsWith(".SHX")) {
527: filename = filename.substring(0, filename.length() - 4);
528: filename += ".FIX";
529: } else {
530: filename += ".fix";
531: }
532: return new URL(filename);
533: }
534:
535: /** Figure out the URL for the "qix" file */
536: public static URL toQixURL(URL url)
537: throws java.net.MalformedURLException {
538: String filename = toFilename(url);
539: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
540: || filename.endsWith(".shx")) {
541: filename = filename.substring(0, filename.length() - 4);
542: filename += ".qix";
543: } else if (filename.endsWith(".SHP")
544: || filename.endsWith(".DBF")
545: || filename.endsWith(".SHX")) {
546: filename = filename.substring(0, filename.length() - 4);
547: filename += ".QIX";
548: } else {
549: filename += ".qix";
550: }
551: return new URL(filename);
552: }
553:
554: /** Figure out the URL for the "grx" file */
555: public static URL toGrxURL(URL url)
556: throws java.net.MalformedURLException {
557: String filename = toFilename(url);
558: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
559: || filename.endsWith(".shx")) {
560: filename = filename.substring(0, filename.length() - 4);
561: filename += ".grx";
562: } else if (filename.endsWith(".SHP")
563: || filename.endsWith(".DBF")
564: || filename.endsWith(".SHX")) {
565: filename = filename.substring(0, filename.length() - 4);
566: filename += ".GRX";
567: } else {
568: filename += ".grx";
569: }
570: return new URL(filename);
571: }
572:
573: /** Figure out the URL for the "prj" file */
574: public static URL toXmlURL(URL url)
575: throws java.net.MalformedURLException {
576: String filename = toFilename(url);
577: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
578: || filename.endsWith(".shx")) {
579: filename = filename.substring(0, filename.length() - 4);
580: filename += ".shp.xml";
581: } else if (filename.endsWith(".SHP")
582: || filename.endsWith(".DBF")
583: || filename.endsWith(".SHX")) {
584: filename = filename.substring(0, filename.length() - 4);
585: filename += ".SHP.XML";
586: } else {
587: filename += ".shp.xml";
588: }
589: return new URL(filename);
590: }
591:
592: /** Figure out the URL for the "prj" file */
593: public static URL toPrjURL(URL url)
594: throws java.net.MalformedURLException {
595: String filename = toFilename(url);
596: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
597: || filename.endsWith(".shx")) {
598: filename = filename.substring(0, filename.length() - 4);
599: filename += ".prj";
600: } else if (filename.endsWith(".SHP")
601: || filename.endsWith(".DBF")
602: || filename.endsWith(".SHX")) {
603: filename = filename.substring(0, filename.length() - 4);
604: filename += ".PRJ";
605: } else {
606: filename += ".prg";
607: }
608: return new URL(filename);
609: }
610:
611: /** Figure out the URL for the "shx" file */
612: public static URL toShxURL(URL url)
613: throws java.net.MalformedURLException {
614: String filename = toFilename(url);
615: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
616: || filename.endsWith(".shx")) {
617: filename = filename.substring(0, filename.length() - 4);
618: filename += ".shx";
619: } else if (filename.endsWith(".SHP")
620: || filename.endsWith(".DBF")
621: || filename.endsWith(".SHX")) {
622: filename = filename.substring(0, filename.length() - 4);
623: filename += ".SHX";
624: } else {
625: filename += ".shx";
626: }
627: return new URL(filename);
628: }
629:
630: /** Figure out the URL for the "dbf" file */
631: public static URL toDbfURL(URL url)
632: throws java.net.MalformedURLException {
633: String filename = toFilename(url);
634: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
635: || filename.endsWith(".shx")) {
636: filename = filename.substring(0, filename.length() - 4);
637: filename += ".dbf";
638: } else if (filename.endsWith(".SHP")
639: || filename.endsWith(".DBF")
640: || filename.endsWith(".SHX")) {
641: filename = filename.substring(0, filename.length() - 4);
642: filename += ".DBF";
643: } else {
644: filename += ".dbf";
645: }
646: return new URL(filename);
647: }
648:
649: /** Figure out the URL for the "shp" file */
650: public static URL toShpURL(URL url)
651: throws java.net.MalformedURLException {
652: String filename = toFilename(url);
653: if (filename.endsWith(".shp") || filename.endsWith(".dbf")
654: || filename.endsWith(".shx")) {
655: filename = filename.substring(0, filename.length() - 4);
656: filename += ".shp";
657: } else if (filename.endsWith(".SHP")
658: || filename.endsWith(".DBF")
659: || filename.endsWith(".SHX")) {
660: filename = filename.substring(0, filename.length() - 4);
661: filename += ".SHP";
662: } else {
663: filename += ".shp";
664: }
665: return new URL(filename);
666: }
667: }
|