How do I create a layer that is generated from an image?

3910
12
Jump to solution
06-18-2012 11:56 PM
BillNalen
New Contributor
I'm trying to add an overlay image to the map in iOS. The iOS MapKit allows me to specify an overlay image to use on top of the map and that's what version 1.0 of my app did. I've changed to use the ArcGIS maps and I want to add the same type of overlay.

I'm able to create the AGSMapView and add it to my view and everything works fine. I'm able to add a graphics layer and add some symbols with callouts just fine too.

I have the image retrieved from the server and I know the envelope of the affected area where I want to overlay the image. I'm just not sure how to add a given image to the map. I've tried:

- Create a custom tiled layer - but after reading a blog post on esri it seemed to indicate that images should use the dynamic layer

- Create a custom dynamic layer - I subclassed AGSDynamicLayer. My subclass returns the right envelope, spatial reference, units and initial envelope. I'm also defining exportMapImage but I don't know what should go in that method. I'm creating a subclass of NSOperation which implements AGSDynamicLayerDrawingOperation but it doesn't seem to do anything. I'm also calling [self.exportDelegate dynamicLayer:self exportMapImageOperation:operation didFinishWithImage:self.mapImage]; in exportMapImage. I can see my method get called and the image and parameters seem good. But no image shows on the map (I've added my custom dynamic layer to the AGSMapView).

- Create a new graphic layer - I created a graphics layer and added my image to it using AGSGraphic graphicWithGeometry:graphicRect symbol:graphicSymbol attributes:graphicAttributes infoTemplateDelegate:self]; where the graphicRect is bounds of my image envelope and the graphicSymbol is [AGSPictureMarkerSymbol pictureMarkerSymbolWithImage:mapImage]; - This does put my image on the map, but it's not scaling correctly when I zoom or move the map. I want it to remain in the specified rectangle even when the map is moved or zoomed.

I'm not sure what direction I should take, I feel like I'm not going about this the right way.

Here's an example of what I want to do:
- go to http://raidsonline.com/
- on the left, pick AZ - Avondale from the Jump To City dropdown
- on the left, check the box in front of density map under the Analytic Layers section.
- the image the shows up on the map is the image I'm retrieving from the server and want to add on top of the map on the iPhone/iPad
0 Kudos
1 Solution

Accepted Solutions
NimeshJarecha
Esri Regular Contributor
The custom dynamic layer sample is available on github.

You should use AGSImageRequestOperation to request an image from requestImageWithWidth method.

Regards,
Nimesh

View solution in original post

0 Kudos
12 Replies
NimeshJarecha
Esri Regular Contributor
You should publish your data as either Map Service or Image Service and use AGSDynamicMapServiceLayer or AGSImageServiceLayer from the SDK and it'll work exactly as you want.

Regards,
Nimesh
0 Kudos
BillNalen
New Contributor
I don't have access to the server code so I have to use what I'm given, just an image and the envelope for the image. Is there a way to subclass one of those classes and just have it draw the image that I give it?
0 Kudos
NimeshJarecha
Esri Regular Contributor
Can you please share your custom dynamic layer code so I can have a look, why it's not working?

Regards,
Nimesh
0 Kudos
BillNalen
New Contributor
In my subclass init method I do this, where heatArea is the visible area of the map translated to WKID 4326 (which I pass to the server to retrieve the image) (my base map is in WKID 102100)
        AGSSpatialReference *baseSR = [AGSSpatialReference spatialReferenceWithWKID:102100];
        AGSGeometryEngine *geometryEngine = [AGSGeometryEngine defaultGeometryEngine];        
        
        AGSPoint *min = [[AGSPoint alloc] initWithX:heatArea.envelope.xmin y:heatArea.envelope.ymin spatialReference:heatArea.envelope.spatialReference];        
        AGSPoint *adjustedMin = (AGSPoint*) [geometryEngine projectGeometry:min toSpatialReference:baseSR];        

        AGSPoint *max = [[AGSPoint alloc] initWithX:heatArea.envelope.xmax y:heatArea.envelope.ymax spatialReference:heatArea.envelope.spatialReference];
        AGSPoint *adjustedMax = (AGSPoint*) [geometryEngine projectGeometry:max toSpatialReference:baseSR];        
        
        densityFullEnvelope = [AGSEnvelope envelopeWithXmin:adjustedMin.x 
                                                     ymin:adjustedMin.y 
                                                     xmax:adjustedMax.x 
                                                     ymax:adjustedMax.y 
                                         spatialReference:baseSR];



I then override these methods (though only the first one ever seems to be called):
- (NSOperation<AGSDynamicLayerDrawingOperation>*)exportMapImage:(AGSExportImageParams*)params
{
    DensityMapOperation *operation = [[DensityMapOperation alloc] initWithParams:params];
    [self.exportDelegate dynamicLayer:self exportMapImageOperation:operation didFinishWithImage:self.mapImage];
    
    return operation;
}
- (AGSUnits)units
{
 return densityUnits;
}

- (AGSSpatialReference *)spatialReference
{
 return densityFullEnvelope.spatialReference;
}

- (AGSEnvelope *)fullEnvelope
{
 return densityFullEnvelope;
}

- (AGSEnvelope *)initialEnvelope
{
 //Assuming our initial extent is the same as the full extent
 return densityFullEnvelope;
}



I didn't find anything in the documentation about what to override in my subclass of NSOperation <AGSDynamicLayerDrawingOperation> so I just overrode the following which are the parameters originally passed in when the object was created:
- (AGSExportImageParams *)exportImageParams
{
    return theParams; 
}


It seems like the following line is the only thing that has an image in it:
[self.exportDelegate dynamicLayer:self exportMapImageOperation:operation didFinishWithImage:self.mapImage];


But why would I pass the image and the operation to the delegate? That seems a little confusing. I was expecting to pass the image to some view that would then use it and draw it on the screen. Finally, I'm adding the layer like this:

    DensityMapLayer *densityMapLayer = [[DensityMapLayer alloc] initWithGeometry:heatArea];    
    densityMapLayer.mapImage = [heatServer.mapImage copy];
    UIView *densityView = [self.mapView addMapLayer:densityMapLayer withName:@"Density Layer"];


Nothing shows on the map even though the image is good (I can throw the image in a UIImageView and it shows properly). I also can't find where the operation created in the method above gets used. It doesn't seem to run or anything, just one property seems to get hit. I was hoping to see something like MapImageLayer from the Javascript library where I could just pass the layer an image and add it to the map.

Hopefully I'm missing something here. I did look through a ton of documentation and the header files, but I didn't see any other methods on AGSDynamicLayer or NSOperation <AGSDynamicLayerDrawingOperation> that would help me.

thanks for any help you can give.
0 Kudos
NimeshJarecha
Esri Regular Contributor
Okie..i just wrote a custom dynamic layer for you...see it attached and you'll get to know what to do. You should add layer like below...however, i'm not sure how to get the correct image id, you know that. Also, you can pass your envelope which you are creating.

    CustomDynamicLayer *customDynamicLayer = [[CustomDynamicLayer alloc] initWithURL:[NSURL URLWithString:@"http://raidsonline.com/ImageService"] imageId:@"9b82ece8-ef75-4002-bcdd-a66b87b49852" envelope:<>];
    [self.mapView addMapLayer:customDynamicLayer withName:@"Custom Dynamic Layer"];

The layer will request the image with given id.

Hope this helps!

Regards,
Nimesh
0 Kudos
BillNalen
New Contributor
Wow, that was great. I don't know how it's assembling the final url to get the image, but apparently it's just tacking on the id using the dictionary passed. So this class (AGSDynamicLayerImageRequestOperation) must be doing the fetching for me based on the dictionary and url passed. I never thought to let the API fetch the image for me, thought I had to do that and then use it. I now get the right image back and it shows on the map.

However, it's still not quite right. It doesn't seem to be doing anything with the envelope and just draws the image over the current visible area. If I zoom or pan the map, the dynamic layer seems to just stay unzoomed and unpanned. That is, the underlying map is zooming and moving but my image remains unchanged from when it's first drawn.

When I fetch the image I'm using the following for the envelope:
heatArea = self.mapView.visibleArea;


When I breakpoint on units, spatialReference, fullEnvelope, or initialEnvelope, I don't see them getting called at all. Is this right? Seems like the layer isn't getting the right envelope picked up somewhere.

Thanks for all the help.
Bill
0 Kudos
BillNalen
New Contributor
Oh I think I see. It's getting passed the exportImageParams with a new envelope and that envelope is probably the current visible area. But I'm returning the same image for each call since I haven't changed my image id yet. This would work if I fetched a new image each time the map moves. But I only fetch a new image when I hit a button rather than when the map moves. I think I can fix what you sent to only get a new image.
0 Kudos
BillNalen
New Contributor
Okay, got it. I just return early from exportMapImage if I've already loaded the image for this envelope. When I request the next image I just delete the dynamic layer and then create a new one. Working perfectly.

Thanks so much for your help.

Bill
0 Kudos
NimeshJarecha
Esri Regular Contributor
Great! Glad to know that everything is working for you now! 🙂

Regards,
Nimesh
0 Kudos