MapSuite Team,
My application supports a shape file that is rather large (shp=138MB, dbf=340MB, ids=12MB, idx=42MB) with 821,000 polygon features. As you might imagine my issue is speed, of course. The drawing of the polygons and labels are based on ValueStyle based on one of five possible column values. I don’t think this is an issue, but rather the query of the records that are needed for drawing the extent.
I’ve been looking at your sample applications on wiki and noticed that there is some type of filtering that can be applied, but filtering as it’s described only filters based on column values which is not an option for me. I was wondering if there is a way to filter some how on distance from a point?
One possible solution would be to draw the features of this large shape file say within a 200 foot radius from said center point. Seems to me I would have to do a spatial query to get all features within the radius and then create an InMemoryFeatureLayer. Is that right or is there another way to do that?
Another solution might be to convert this shape file to an sql layer. Is an MsSql2008FeatureSource more efficiently accessed as opposed to a ShapeFileFeatureLayer?
Any suggestions and assistance appreciated.
Regards,
Dennis
Efficient Drawing of Large ShapeFileFeatureLayer
Hi Dennis,
I guess the problem just shows at the high zoom level, where
all the features should show up. Actually we are working on some algorithms to
resolve the problem now, something like filter the minor/tiny priority roads or
very small roads, but it’s in progress, may takes a bit long time.
I guess it may run into memory issue or performance issue as
well if we do the query and save result into an InMemoryFeatureLayer, maybe converting
it to MsSql2008FeatureSource is an option, but still unable to avoid drawing
all features in current extent, even if the drawn shape on the map if really small.
Here are some ideas I can get:
1. Create a customized ShapeFileFeatureSource,
maybe we can call it “MyShapeFileFeatureSource”.
2. Overwrite the GetFeaturesForDrawingCore method, maybe
the code like the following:
public
class
MyShapeFileFeatureSource : ShapeFileFeatureSource
{
private
double
tolerance;
/// <summary>
/// Get a tolerance for filter the features for drawing whose size is small than it.
/// </summary>
public
double
Tolerance
{
get
{
return
tolerance; }
set
{ tolerance = value; }
}
protected
override
Collection<Feature> GetFeaturesForDrawingCore(RectangleShape boundingBox,
double
screenWidth,
double
screenHeight, IEnumerable<
string
> returningColumnNames)
{
// Get all feature ids for drawing, it should be really fast.
Collection<
string
> featureIdsInBbox = GetFeatureIdsForDrawing(boundingBox, screenWidth, screenHeight);
Collection<
string
> featureIdsForDrawing =
new
Collection<
string
>();
// Filter the features that its drawn size is really small than a specified width/height.
double
resolutionX = boundingBox.Width / screenWidth;
double
resolutionY = boundingBox.Height / screenHeight;
foreach
(
string
id
in
featureIdsInBbox)
{
RectangleShape bbox = GetBoundingBoxById(id);
// Calculate the width and height of the feature drawn on the map under current scale
double
drawingWidth = bbox.Width / resolutionX;
double
drawingHeight = bbox.Height / resolutionY;
// just add the feature whose drawn size is bigger than sepcified tolerance.
if
(Math.Max(drawingWidth, drawingHeight) > Tolerance)
{
featureIdsForDrawing.Add(id);
}
}
return
base
.GetFeaturesByIds(featureIdsForDrawing, returningColumnNames);
}
}
Thanks,
Johnny
Johnny,
Thanks for your response.
I am using ShapeFileFeatureLayer. How do I apply the ShapeFileFeatureSource from your sample code to my ShapeFileFeatureLayer?
Thanks,
Dennis
Hi Dennis,
You can implement that like this:
public class MyShapeFileFeatureLayer : ShapeFileFeatureLayer
{
public MyShapeFileFeatureLayer(string shapePathFilename)
: base(shapePathFilename)
{
FeatureSource = new MyShapeFileFeatureSource();
}
}
MyShapeFileFeatureLayer layer = new MyShapeFileFeatureLayer("");
Regards,
Don
hi Don,
Thanks for you reply.
I implemented your sample code and my layer loads/renders. However, I set a breakpoint in the GetFeaturesForDrawingCore override method but the breakpoint is never hit.
I guess I’m not understanding how this is to be coded. Would you give me some additional hints on how to implement this?
Thanks,
Dennis
Hi Dennis,
It may caused by something wrong of the code for implementing “MyShapeFileFeatureLayer”, it should be:
public
class
MyShapeFileFeatureLayer : ShapeFileFeatureLayer
{
public
MyShapeFileFeatureLayer(
string
shapePathFilename)
{
FeatureSource =
new
MyShapeFileFeatureSource(shapePathFilename);
}
}
Otherwise it doesn’t take use of the customized FeatureSource.
Thanks,
Johnny
Johnny,
I apologize, but this still does not make sense to me.
The following line of code does not match MyShapeFileFeatureSource class that you had defined last week –
FeatureSource = new
MyShapeFileFeatureSource(shapePathFilename);
How does MyShapeFileFeatureSource need to be modified to accommodate shapePathFilename?
Also, how do you instantiate the MyShapeFileFeatureLayer with a shape file?
Appreciate your assistance.
Dennis
Hi Dennis,
Each ShapeFileFeatureLayer contains a ShapeFileFeatureSource, you choose to use custom FeatureSource, but you cannot directly use that in your code, so you have to implement a custom FeatureLayer and assign a custom FeatureSource to it.
So Johnny’s code help you implement this custom FeatureLayer “MyShapeFileFeatureLayer”, you can just think it’s ShapeFileFeatureLayer and replace your original ShapeFileFeatureLayer.
For example:
ShapeFileFeatureLayer layer = new ShapeFileFeatureLayer(“Your Data Path”);
After you have implemented both MyShapeFileFeatureLayer and MyShapeFileFeatureSource, you need call that:
MyShapeFileFeatureLayer layer = new MyShapeFileFeatureLayer(“Your Data Path”);
Please use the MyShapeFileFeatureLayer just like you use ShapeFileFeatureLayer in your code and the new MyShapeFileFeatureSource will works.
For shapePathFilename, that’s means for sample we just implemented this one constructor of ShapeFileFeatureLayer, if you need other constructor you can implement that just like what we did.
Any question please let us know.
Regards,
Don
Don,
Thanks for your response.
I apologize, but I am still not fully understanding what has to be done to accomplish this in terms of concrete code.
I have implemented the code as shown below
public
class
MyShapeFileFeatureLayer : ShapeFileFeatureLayer
{
public
MyShapeFileFeatureLayer(
string
ThePathFile,
string
ThePathIndex)
{
this
.ShapePathFileName = ThePathFile;
this
.IndexPathFileName = ThePathIndex;
this
.FeatureSource =
new
MyShapeFileFeatureSource(ThePathFile, ThePathIndex);
}
}
public
class
MyShapeFileFeatureSource : ShapeFileFeatureSource
{
private
double
tolerance;
public
MyShapeFileFeatureSource(
string
ThePathFile,
string
ThePathIndex)
{
this
.ShapePathFileName = ThePathFile;
this
.IndexPathFileName = ThePathIndex;
}
///
/// Get a tolerance for filter the features for drawing whose size is small than it.
///
public
double
Tolerance
{
get
{
return
tolerance; }
set
{ tolerance = value; }
}
public
ShapeFileFeatureSource GetShapeFileFeatureSource()
{
return
(
this
);
}
protected
override
Collection<Feature> GetFeaturesForDrawingCore(RectangleShape boundingBox,
double
screenWidth,
double
screenHeight, IEnumerable<
string
> returningColumnNames)
{
// Get all feature ids for drawing, it should be really fast.
Collection<
string
> featureIdsInBbox = GetFeatureIdsForDrawing(boundingBox, screenWidth, screenHeight);
Collection<
string
> featureIdsForDrawing =
new
Collection<
string
>();
// Filter the features that its drawn size is really small than a specified width/height.
double
resolutionX = boundingBox.Width / screenWidth;
double
resolutionY = boundingBox.Height / screenHeight;
foreach
(
string
id
in
featureIdsInBbox)
{
RectangleShape bbox = GetBoundingBoxById(id);
// Calculate the width and height of the feature drawn on the map under current scale
double
drawingWidth = bbox.Width / resolutionX;
double
drawingHeight = bbox.Height / resolutionY;
// just add the feature whose drawn size is bigger than sepcified tolerance.
if
(Math.Max(drawingWidth, drawingHeight) > Tolerance)
{
featureIdsForDrawing.Add(id);
}
}
return
base
.GetFeaturesByIds(featureIdsForDrawing, returningColumnNames);
}
}
And then I create the layer with the following:
MyShapeFileFeatureLayer lBuildings =
new
MyShapeFileFeatureLayer(
"C:\MapData\Buildings.shp"
,
"C:\MapDataIndex\Buildings.idx"
);
I set a breakpoint at GetFeaturesForDrawingCore, but the breakpoint is never hit. However, the layer actually renders.
Your thoughts?
Thanks,
Dennis
Hi Dennis,
I apologize that I missed that the GetFeatureIdsForDrawing is called from Draw method internally as well. Thus we need to overwrite this method as well, to make sure we can understand each other, please check the attached sample, the following is the screenshot we got from it, you see some countries removed from the map.
Thanks,
Johnny
Post12513.zip (1.5 MB)
Johnny,
Thanks very much for the example. I can now set/get a breakpoint in GetFeatureIdsForDrawingCore. However, I get no breakpoint in GetFeaturesForDrawingCore. Should that breakpoint get hit?
My original goal with this is to speed up the drawing of this 821,000 multi-polygon layer. I also apply five AreaStyles and five TextStyles.
This doesn’t really appear to draw any faster.
Even if zoomed in and rendering only 500 or less polygons it can take up to a minute or more to render.
The base.GetFeatureIdsForDrawingCore actually reads the features very fast. The problem is the read of the dbf file in order to retrieve the column attributes. Just in a ‘small’ area it can take ten seconds+ to draw. My hard drive is going like crazy reading the dbf file.
What is the access path from each FeatureId to its’ corresponding columns in the dbf? This is definitely where the delay is coming from.
I am using ids & idx with this layer.
Thanks,
Dennis
Johnny,
I placed your sample code in my full application and have a video of centering the map on a Lat/Long, based on address, and you will see the time required to draw the complete rendered map. Keep in mind that I have 68 ShapeFileFeatureLayer, plus 16 MrSid Layers.
Were can I email you this video?
Thanks,
Dennis
Hi Dennis,
In ShapeFileFeatureLayer/ShapeFileFeatureSource, the GetFeaturesForDrawingCore can be called just under some special circumstances, most of the time it just can hit GetFeatureIdsForDrawingCore, thus, don’t need to worry about GetFeaturesForDrawingCore is not called in your application.
Just as I mentioned, the reason why creating the customized ShapeFileFeatureSource is removing some very small polygons from drawing, at very top-high zoom level. To display the lables of each polygon, the Map Suite just reads the required column from dbf, normally, only one column wil be read, thus, I guess it’s not too slow. Would you please send your test project, including the test data (821,000 multi-polygon) to forumsupport@thinkgeo.com, then we can dig into further to see what happened there?
Ps. one more thing that you can do is removing the TextStyles from the layer to see if there is any drawing performance improved.
Thanks,
Johnny
Hi Dennis,
I don’t know you have so many layers loaded to the map before, how many LayerOverlay created for these layers? just only 2 LayerOverlay, one for ShapeFileFeatureLayers and another for MrSid layers? Are these layers will be taken as static or will be changed following the operations from users? If it’s static layers, I suggest you creating Tile-Cache for them, it will give a very good user-experience.
Thanks,
Johnny
Johnny,
The rendering is very slow even for only 500 or so of the polygons.
I’m sure it is the reading of the dbf file to retrieve the column information. What is the access patch from the FeatureId to the dbf record for the feature? This has to be were the slowdown is occurring.
I don’t have the option of removing the Area or Text Styles as these have to be rendered.
Thanks,
Dennis
Johnny,
I have only two LayerOverlays. One for the MrSids and one for the ShapeFileFeatureLayers. The LayerOverly for the MrSids is tiled.
The layers are ReadOnly.
I did not think that tiling for the ShapeFileFeatureLayer’s would work in my case. For example, if the MrSid layer is displayed polygons, like parks, water, cemeteries, are rendered with an outline. The user has the ability to turn off the MrSids and also when zooming far out the MrSids are turned off automatically. In this case the polygons are rendered with a solid AreaStyle. So not sure that tiling will work under these conditions.
Thanks,
Dennis
Hi Dennis,
The method “GetColumn” of GeoDbf is used to read the column from dbf. You can do a simple test to see if this is the problem, if not, could you please creat a sample for us?
For removing the TextSytles from polygon layers, it’s just a simple test to determine if the slowdown of performance comes from reading from dbf, please do a test and let us know what’s result.
Thanks,
Johnny
Johnny,
I’d like to know the underlying access from FeatureId to dbf, not the top most method (GetColumn). What is the indexing method used? Should not the FeatureId itself be a ‘key’ into the dbf?
I sent you an email with a Dropbox link to the video of my map rendering.
Thanks,
Dennis
Johnny,
In my test application I removed the AreaStyles & TextStyles and it does draw much faster, but certainly not instantaneous, especially when drawing on a 22" or 24" monitor.
And remember I’m only rendering a small portion of the features at any given time. To render all the features with no styles takes probably four + minutes.
The key here is how to improve the access method from FeatureId to Column data. I need this applied to all my layers.
It is late here so I will have to send you my test application and data tomorrow as I would like to clean it up and then I will download the shape file to Dropbox.
Thanks,
Dennis