Opening shapefiles using FeatureTable and FeatureLayer.

4965
8
02-04-2014 09:09 PM
MarcinRoguski
New Contributor
Hello,

recently I was trying to open a shapefile on Android and display it on the map.
I am able to load the shapefile data but displaying it on the map is a little bit complicated.
I tried to use FeatureLayer and FeatureTable to do this, but unfortunately there was no visible map on the screen.
It it even possible to display shapefiles this way ?

Here is my code for FeatureLayer:
public class ShapefileLayer extends FeatureLayer {
    private String url;
    
    public ShapefileLayer(String shpPath, Envelope fullExtent) {
        super(new ShapefileFeatureTable(shpPath));
        url = new File(shpPath).getAbsolutePath();
        this.setFullExtent(fullExtent);
        this.setInitialExtent(fullExtent);
        this.setVisible(true);
        this.initLayer();
        this.changeStatus(STATUS.INITIALIZED);
    }
    
    @Override
    public double getMaxScale() {
        return 100000;
    }
    
    @Override
    public double getMinScale() {
        return 1;
    }
    
    @Override
    public String getUrl() {
        return url;
    }
}



The other three classes are in next post because of the characters limit 🙂


In my MainActivity I add:
mapView.addLayer(new ShapefileLayer("/mnt/sdcard2/shp/gaz.shp", fullLayerExtent));

but I see only black screen or other layers if I add them too.
The file from sdCard is available and I am able to open it for reading and writing so no problems there.

These classes are very simple and do not include all the features I plan to add. It is made to only load geometry and display it on the map.
0 Kudos
8 Replies
MarcinRoguski
New Contributor
And here is my implementation of the feature table:
public class ShapefileFeatureTable extends FeatureTable {
    private String tableName;
    private boolean hasGeometry;
    private SpatialReference spatialReference;
    private Map<String, Field> fields = new HashMap<String, Field>(2);
    private Map<Long, Feature> features = new HashMap<Long, Feature>();
    
    private SimpleLineSymbol sls = new SimpleLineSymbol(Color.BLUE, 8);
    private long featureId = 1;
    
    public ShapefileFeatureTable(String shapefilePath) {      
        tableName = shapefilePath.substring(shapefilePath.lastIndexOf('/') + 1, shapefilePath.lastIndexOf('.'));
        
        //TODO: load from shp
        fields.put("OID", new ShapefileField("OID", Field.esriFieldTypeOID, 8));
        fields.put("SHAPE", new ShapefileField("SHAPE", Field.esriFieldTypeGeometry, -1));
        hasGeometry = true;
        spatialReference = SpatialReference.create(SpatialReference.WKID_WGS84);
        
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        try {
            fis = new FileInputStream(shapefilePath);
            bis = new BufferedInputStream(fis);
            ShapeFileReader reader = new ShapeFileReader(bis);
            AbstractShape shape = reader.next();
            while(shape != null) {
                this.addFeature(new ShapefileFeature(shape,
                        null,
                        spatialReference, sls));
                shape = reader.next();
            }
        } catch (Exception e) {
            
        }
    }
    
    @Override
    public long addFeature(Feature feature) throws TableException {
        if(feature instanceof ShapefileFeature) {
            this.validateSchema(feature);
            long id = featureId++;
            ((ShapefileFeature) feature).setId(id);
            features.put(id, feature);
            return id;
        }
        throw new TableException("You can only add type: ShapefileFeature!');
    }

    @Override
    public long[] addFeatures(List<Feature> features) throws TableException {
        if(features != null && features.size() > 0) {
            long[] result = new long[features.size()];
            int index = 0;
            for(Feature feature : features) {
                result[index++] = this.addFeature(feature);
            }
            return result;
        }
        return new long[0];
    }

    @Override
    public void deleteFeature(long featureId) throws TableException {
        features.remove(featureId);
    }

    @Override
    public void deleteFeatures(long[] featureIds) throws TableException {
        if(featureIds != null && featureIds.length > 0) {
            for(long featureId : featureIds) {
                this.deleteFeature(featureId);
            }
        }
    }

    @Override
    public String getCopyright() {
        return "C";
    }

    @Override
    public Feature getFeature(long id) throws TableException {
        return features.get(id);
    }

    @Override
    public FeatureResult getFeatures(long[] ids) {
        ShapefileFeatures result = new ShapefileFeatures();
        if(ids != null && ids.length > 0) {
            List<Object> features = new ArrayList<Object>(ids.length);
            for(long id : ids) {
                features.add(this.features.get(id));
            }
            result.setFeatures(this.features.values());
        }
        return result;
    }

    @Override
    public Field getField(String fieldName) {
        return fields.get(fieldName);
    }

    @Override
    public List<Field> getFields() {
        return new ArrayList<Field>(fields.values());
    }

    @Override
    public String getTableName() {
        return tableName;
    }

    @Override
    public boolean hasGeometry() {
        return hasGeometry;
    }

    @Override
    public boolean isEditable() {
        return true;
    }

    @Override
    public Future<FeatureResult> queryFeatures(final QueryParameters queryParameters, final CallbackListener<FeatureResult> callbackListener) {
        return new FutureTask<FeatureResult>(new Callable<FeatureResult>() {

            public FeatureResult call() throws Exception {
                try {
                    FeatureResult result = ShapefileFeatureTable.this.queryFeatures(queryParameters);
                    callbackListener.onCallback(result);
                    return result;
                } catch(Exception e) {
                    callbackListener.onError(e);
                    throw e;
                }
            }
        });
    }

    @Override
    public Future<long[]> queryIds(final QueryParameters queryParameters, final CallbackListener<long[]> callbackListener) {
        return new FutureTask<long[]>(new Callable<long[]>() {

            public long[] call() throws Exception {
                try {
                    FeatureResult result = ShapefileFeatureTable.this.queryFeatures(queryParameters);
                    long[] ids = new long[(int) result.featureCount()];
                    
                    Iterator<Object> iterator = result.iterator();
                    int index = 0;
                    while(iterator.hasNext()) {
                        Object obj = iterator.next();
                        if(obj instanceof Feature) {
                            ids[index++] = ((Feature) obj).getId();
                        }
                    }
                    callbackListener.onCallback(ids);
                    return ids;
                } catch(Exception e) {
                    callbackListener.onError(e);
                    throw e;
                }
            }
        });
    }
    
    private FeatureResult queryFeatures(QueryParameters queryParameters) {
        int maxFeatures = queryParameters.getMaxFeatures();
        if(maxFeatures <= 0) {
            maxFeatures = Integer.MAX_VALUE;
        }
        
        List<Feature> features = null;
        if(queryParameters.getObjectIds() != null && queryParameters.getObjectIds().length > 0) {
            features = new ArrayList<Feature>(Math.min(maxFeatures, queryParameters.getObjectIds().length));
            for(long id : queryParameters.getObjectIds()) {
                features.add(this.features.get(id));
            }
        }
        Geometry g = queryParameters.getGeometry();
        if(g != null) {
            SpatialRelationship sr = queryParameters.getSpatialRelationship();
            if(sr == null) {
                throw new IllegalArgumentException("Nie podano typu relacji przestrzennej!");
            }
            if(features != null) {
                features = GeometryUtils.filterFeatures(features, g, sr, maxFeatures);
            } else {
                features = GeometryUtils.filterFeatures(this.features, g, sr, maxFeatures);
            }
        }
        
        ShapefileFeatures result = new ShapefileFeatures();
        result.setFeatures(features);
        return result;
    }

    @Override
    public void updateFeature(long featureId, Feature feature) throws TableException {
        this.validateSchema(feature);
        if(features.containsKey(featureId)) {
            features.put(featureId, feature);
        }
    }

    @Override
    public void updateFeatures(long[] ids, List<Feature> features) throws TableException {
        if(ids != null && features != null && ids.length > 0 && ids.length == features.size()) {
            int index = 0;
            for(Feature feature : features) {
                this.updateFeature(ids[index++], feature);
            }
        }
    }
    
    private void validateSchema(Feature feature) throws TableException {
        //TODO
    }

    public static class ShapefileFeatures extends FeatureResult implements Iterator<Object> {
        private List<Feature> features;
        private int index;
        
        private ShapefileFeatures() { }
        
        private void setFeatures(Collection<Feature> features) {
            if(features instanceof List) {
                this.features = (List<Feature>)features;
            } else {
                this.features = new ArrayList<Feature>(features);
            }
        }
        
        @Override
        public Iterator<Object> iterator() {
            return this;
        }
        
        @Override
        public long featureCount() {
            return features.size();
        }
        
        public boolean hasNext() {
            return index < features.size() - 1;
        }

        public Object next() {
            return this.hasNext() ? features.get(index++) : null;
        }

        public void remove() {
            features.remove(index);
        }
    }
    
    private static class ShapefileField extends Field {
        private String name;
        private int type;
        private int length;
        
        public ShapefileField(String name, int type, int length) {
            this.name = name;
            this.type = type;
            this.length = length;
        }
        
        @Override
        public String getAlias() {
            return name;
        }
        
        @Override
        public Domain getDomain() {
            return null;
        }
        
        @Override
        public int getFieldType() {
            return type;
        }
        
        @Override
        public String getName() {
            return name;
        }
        
        @Override
        public int getLength() {
            return length;
        }
    }
}
0 Kudos
MarcinRoguski
New Contributor
The ShapefileFeature class:
public class ShapefileFeature implements Feature {
    private long id;
    private AbstractShape abstractShape;
    private Geometry geometry;
    private Map<String, Object> attributes;
    private SpatialReference spatialReference;
    private Symbol symbol;

    public ShapefileFeature(AbstractShape abstractShape,
            Map<String, Object> attributes, SpatialReference spatialReference,
            Symbol symbol) {
        this.abstractShape = abstractShape;
        this.attributes = attributes;
        this.spatialReference = spatialReference;
        this.symbol = symbol;
    }
    
    /*package*/ void setId(long id) {
        this.id = id;
    }

    public Object getAttributeValue(String fieldName) {
        return attributes == null ? null : attributes.get(fieldName);
    }

    public Map<String, Object> getAttributes() {
        return attributes;
    }

    public Geometry getGeometry() {
        if(geometry == null) {
            geometry = this.toGeometry(abstractShape);
        }
        return geometry;
    }

    public long getId() {
        return id;
    }

    public SpatialReference getSpatialReference() {
        return spatialReference;
    }

    public Symbol getSymbol() {
        return symbol;
    }

    private Geometry toGeometry(AbstractShape ps) {
        if(ps instanceof PolylineShape) {
            Polyline result = new Polyline();
           
            PointData[] points = ((PolylineShape)ps).getPoints();
            for(int i=0;i<points.length - 1;++i) {
                Line linePart = new Line();
                linePart.setStart(new Point(points.getX(), points.getY()));
                linePart.setEnd(new Point(points[i + 1].getX(), points[i + 1].getY()));
                result.addSegment(linePart, result.getPathCount() == 0);
            }
            return result;
        }
        //TODO: other geometries will be implemented later
        throw new IllegalStateException("Not yet implemented!");
    }
}


And at last GeometryUtils that is made to filter objects by geometry and is used in spatial queries:
public class GeometryUtils {

    private static Map<SpatialRelationship, GeometryRelationFilter> FILTERS = new HashMap<SpatialRelationship, GeometryRelationFilter>();
    static {
        FILTERS.put(SpatialRelationship.CONTAINS, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                return GeometryEngine.contains(left, right, sr);
            }
        });
        FILTERS.put(SpatialRelationship.CROSSES, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                return GeometryEngine.crosses(left, right, sr);
            }
        });
        FILTERS.put(SpatialRelationship.ENVELOPE_INTERSECTS, new GeometryRelationFilter() {
            private Envelope leftGeomExtent = new Envelope();
            private Envelope rightGeomExtent = new Envelope();
            
            public synchronized boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                left.queryEnvelope(leftGeomExtent);
                right.queryEnvelope(rightGeomExtent);
                
                Envelope leftEnvelope = leftGeomExtent.getXMin() <= rightGeomExtent.getXMin() ? leftGeomExtent : rightGeomExtent;
                Envelope rightenvelope = leftEnvelope == leftGeomExtent ? rightGeomExtent : leftGeomExtent;
                if(leftEnvelope.getXMax() < rightenvelope.getXMin()) {
                    return false;
                }
                
                Envelope topEnvelope = leftGeomExtent.getYMax() >= rightGeomExtent.getYMax() ? leftGeomExtent : rightGeomExtent;
                Envelope bottomEnvelope = topEnvelope == leftGeomExtent ? rightGeomExtent : leftGeomExtent;
                return topEnvelope.getYMin() < bottomEnvelope.getYMax();
            }
        });
        FILTERS.put(SpatialRelationship.INTERSECTS, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                Geometry intersection = GeometryEngine.intersect(left, right, sr);
                return intersection != null && !intersection.isEmpty();
            }
        });
        FILTERS.put(SpatialRelationship.TOUCHES, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                return GeometryEngine.touches(left, right, sr);
            }
        });
        FILTERS.put(SpatialRelationship.WITHIN, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                return GeometryEngine.within(left, right, sr);
            }
        });
        FILTERS.put(SpatialRelationship.OVERLAPS, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                if(left.getDimension() != right.getDimension()) {
                    Geometry intersection = GeometryEngine.intersect(left, right, sr);
                    return intersection.getDimension() == left.getDimension();
                }
                throw new IllegalArgumentException("Relacja pokrywania (overlap) jest nieokre�?lona dla różnych typów geometrii!");
            }
        });
        FILTERS.put(SpatialRelationship.INDEX_INTERSECTS, new GeometryRelationFilter() {
            public boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr) {
                //TODO
                throw new IllegalStateException("Relacja INDEX_INTERSECTS jest niezaimplementowana!");
            }
        });
    }
    
    public static List<Feature> filterFeatures(Map<Long, Feature> features,
            Geometry filterGeometry, SpatialRelationship sr, int maxFeatures) {
        GeometryRelationFilter filter = FILTERS.get(sr);
        
        List<Feature> result = new ArrayList<Feature>(500 > features.size() ? 500 : features.size());
        for(Entry<Long, Feature> feature : features.entrySet()) {
            Feature f = feature.getValue();
            if(filter.isRelationvalid(filterGeometry, f.getGeometry(), f.getSpatialReference())) {
                result.add(f);
            }
            if(result.size() >= maxFeatures) {
                break;
            }
        }
        return result;
    }
    
    public static List<Feature> filterFeatures(Collection<Feature> features,
            Geometry filterGeometry, SpatialRelationship sr, int maxFeatures) {
        GeometryRelationFilter filter = FILTERS.get(sr);
        
        List<Feature> result = new ArrayList<Feature>(500 > features.size() ? 500 : features.size());
        for(Feature f : features) {
            if(filter.isRelationvalid(filterGeometry, f.getGeometry(), f.getSpatialReference())) {
                result.add(f);
            }
            if(result.size() >= maxFeatures) {
                break;
            }
        }
        return result;
    }

    private static interface GeometryRelationFilter {
        boolean isRelationvalid(Geometry left, Geometry right, SpatialReference sr);
    }
}
0 Kudos
MarcinRoguski
New Contributor
Really? Nobody tried to read shapefiles this way using FeatureTables ?

I know I posted lots of code, but it really is not that dificult to understand 😉

Even an information that what I am doing is not possible would be valuable.
It would definitely save me a lot time.
0 Kudos
RobertBurke
Esri Contributor
Hi Marcin,

Please have a look at this thread posts #6 and #7:

http://forums.arcgis.com/threads/94377-offline-map-viewing


And also the offline map discussion here:

https://developers.arcgis.com/android/guide/create-an-offline-map.htm

Hope this helps!
0 Kudos
cjl
by
New Contributor
you can extends GeodatabaseFeatureTable,then override all methods.

package com.esri.core.geodatabase;

public class ShapefileFeatureTable extends GeodatabaseFeatureTable{
     public ShapefileFeatureTable( ){
        super();
    }
}

still use FeatureLayer,FeatureLayer only use GeodatabaseFeatureTable�??or subclass�??


public FeatureLayer(FeatureTable featureTable)
  {
    super(false);

    if (featureTable == null) {
      throw new IllegalArgumentException("The argument featureTable cannot be null.");
    }

    this.u = featureTable;

    if (featureTable instanceof GeodatabaseFeatureTable) {
      this.v = true;
      a((GeodatabaseFeatureTable)featureTable);
      setCredentials(((GeodatabaseFeatureTable)featureTable).getGeodatabase().getCredentials());
    } else {
      this.v = false;
    }

    HashMap localHashMap = new HashMap();
    localHashMap.put(Integer.valueOf(0), createPopupInfo());
    setPopupInfos(localHashMap);
  }
0 Kudos
MotiejusViltrakis
New Contributor II

Hello Marcin,

Have you managed to complete your task yet? I recently started looking into it myself and need some help with it.

Steb by step process of how to make a shapetable, add a shape file and display it would be great if somebody could help out

0 Kudos
PuneetPrakash
Esri Contributor

ArcGIS Runtime SDK for Android 10.2.5 supports direct read of shape files. Feature editing is not supported at this time.

ShapefileFeatureTable | ArcGIS Android 10.2.5 API

mShapefilePath = "/shapefiles_data/Data/Points.shp";

    mSdcard = Environment.getExternalStorageDirectory().getPath();
  
    mMapView = (MapView) findViewById(R.id.mapView);
    mTiledlayer = new ArcGISTiledMapServiceLayer("http://services.arcgisonline.com/arcgis/rest/services/ESRI_Imagery_World_2D/MapServer");

    mMapView.addLayer(mTiledlayer);
  
    try {
      mTable = new ShapefileFeatureTable(mSdcard+mShapefilePath);
      mFlayer = new FeatureLayer(mTable);
      mFlayer.setRenderer(new SimpleRenderer(new SimpleMarkerSymbol(Color.MAGENTA, 10, STYLE.TRIANGLE)));
    
      mMapView.addLayer(mFlayer);
      Log.d("**ShapefileTest**", "SpatialReference : "+ mTable.getSpatialReference());
    } catch (FileNotFoundException e) {
      Log.d("**ShapefileTest**", "File not found in SDCard, nothing to load");
      Toast.makeText(getApplicationContext(), "File not found in SDCard, nothing to load", Toast.LENGTH_LONG).show();
    }
MotiejusViltrakis
New Contributor II

Thank you

0 Kudos