I would have an openlayers vector layer with features scattered all over the map. I want to be able to click on a feature and have a message display.
I'm not sure if there is a way to add a listener/handler to each feature.
Any ideas?
Add SelectFeture control:
var selectFeature = new OpenLayers.Control.SelectFeature(vector_layer);
map.addControl(selectFeature);
selectFeature.activate();
After that you can listen to select/unselect events on vector layer:
vector_layer.events.on({
'featureselected': function(feature) {
//display your message here
},
'featureunselected': function(feature) {
//hide message
}
});
You need to use a combination of the SelectFeature control and one of the OpenLayers.Popup classes such as OpenLayers.Popup.FramedCloud. Here is an example of just that:
http://openlayers.org/dev/examples/select-feature-openpopup.html
In that example, try using the "draw polygon" option to draw a polygon (double-click on the map to complete the polygon). Then use "select polygon on click" and click on the polygon, and you will get a framed cloud popup.
You can view the source for the page to see how it this is done. Here are the relevant parts of the code. You can, of course, change the message to whatever you want to display in the framed cloud:
var map = <your OpenLayers.Map object>;
var polygonLayer = <your vector layer>;
selectControl = new OpenLayers.Control.SelectFeature(polygonLayer,
{onSelect: onFeatureSelect, onUnselect: onFeatureUnselect});
map.addControl(selectControl); // not in the example, but do this
function onPopupClose(evt) {
selectControl.unselect(selectedFeature);
}
function onFeatureSelect(feature) {
var message = "<div style='font-size:.8em'>Feature: " + feature.id +"<br>Area: " + feature.geometry.getArea()+"</div>";
selectedFeature = feature;
popup = new OpenLayers.Popup.FramedCloud("chicken",
feature.geometry.getBounds().getCenterLonLat(),
null,
message,
null, true, onPopupClose);
feature.popup = popup;
map.addPopup(popup);
}
function onFeatureUnselect(feature) {
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
Here are the references for the controls you will be using:
http://dev.openlayers.org/apidocs/files/OpenLayers/Control/SelectFeature-js.html
http://dev.openlayers.org/apidocs/files/OpenLayers/Popup/FramedCloud-js.html
If there are many vector layers is it necessary to write "layer_name.events.on ..." for each layer? Is it possible to make a list of layers and assign ".events.on" to all of them?
Related
I need to add a bunch of pins on a MapsUI map control that uses OpenStreetMap tiles.
The problem is I cant find anything mentioning pins in their documentation so my question is:
Are there any pins available but have different names or i have to draw pins myself (using Geometry and Points as in some examples i found) and if so how do i keep them the same size when zooming the map ?
Maybe someone can point out where should i look in their documentation in case I'm blind and missed it.
Thanks!
Your Mapsui Map View Mapsui.UI.Forms.MapView has property Pins. You can add pins there.
You can access MapView View from code-behind *xaml.cs of your MapView.
For that, first, name your Map View in XAML Page:
<ContentPage
xmlns:mapsui="clr-namespace:Mapsui.UI.Forms;assembly=Mapsui.UI.Forms"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="YourProject.Pages.YourMap">
<ContentPage.Content>
<mapsui:MapView
x:Name="selectMapControl"/>
</ContentPage.Content>
</ContentPage>
Then access it from C# code-behind of that Page:
using Mapsui.UI.Forms;
protected override async void OnAppearing()
{
selectMapControl.Pins.Add(new Pin(new MapView())
{
Position = new Position(0, 0),
Type = PinType.Pin,
Label = "Zero point",
Address = "Zero point",
});
}
Hope this simplest example will help.
The idea is that you use your own bitmaps to draw as 'pins'.
Mapsui has features. A feature has a geometry which can be a point, linestring and polygon (and some others). A feature is drawn with some kind of style. What you need to do is create features with point geometry and use a symbolstyle with a bitmap as symbol. You need to register your bitmap and use the bitmapId in the symbol. See the sample here:
https://github.com/Mapsui/Mapsui/blob/master/Samples/Mapsui.Samples.Common/Maps/PointsSample.cs
You can use Xamarin.Forms.GoogleMaps as they give you the feature to add multiple pins of your choice.
Here is the sample code :
var position = new Position(Latitude, Longitude);
var pin = new Pin
{
Type = PinType.Place,
Position = position,
Icon = BitmapDescriptorFactory.FromBundle("pin.png"),
Label = "custom pin",
Address = "custom detail info",
};
MyMap.Pins.Add(pin);
The following works on version 3.02. I've not checked it on any other version of MapSui.
First make sure your pin Bitmap is an embedded resource. You can then get the Bitmap ID like this:
var assembly = typeof(YourClass).GetTypeInfo().Assembly;
var image = assembly.GetManifestResourceStream("YourSolution.YourProject.AFolder.image.png");
If var image returns null, then the image was not found and it's likely not an embedded resource or you got the address/name wrong.
var ID = BitmapRegistry.Instance.Register(image);
The BitmapRegistry method also registers the BitMap for MapSui to use later. I think if it's your first image registered it will be 0.
Then you can create a memory layer as follows:
MemoryLayer PointLayer = new MemoryLayer
{
Name = "Points",
IsMapInfoLayer=true,
DataSource = new MemoryProvider(GetListOfPoints()),
Style = new SymbolStyle { BitmapId = ID, SymbolScale = 0.50, SymbolOffset = new Offset(0, bitmapHeight * 0.5) };
};
The DataSource can be generated as follows (I'm just adding one feature, but you can add as many as you like):
private IEnumerable<IFeature> GetListOfPoints()
{
List<IFeature> list = new List<IFeature>();
var feature = new Feature();
feature.Geometry = new Point(-226787, 7155483);
feature["name"] = "MyPoint";
list.Add(feature);
IEnumerable<IFeature> points = list as IEnumerable<IFeature>;
return points;
}
Then add the new MemoryLayer to your Map as follows:
MapControl.Map?.Layers.Add(PointLayer);
I am dynamically adding kml files to google earth. For this, I have written javascript functions to add a kml and to remove a kml. These functions work fine for the first time for a kml. But if called again they do not respond. This happens for each kml that I try to add or remove. If I keep the page on browser for some time, then these functions again respond once and again become unresponsive.
function add(id, fileurl)
{
var link = ge.createLink('');
var href= fileurl;
link.setHref(href);
var networkLink = ge.createNetworkLink("'" + id + "'");
networkLink.set(link, true, true);
ge.getFeatures().appendChild(networkLink);
}
function remove(id)
{
for(var i=0; i<ge.getFeatures().getChildNodes().getLength(); i++)
{
if(ge.getFeatures().getChildNodes().item(i).getId() == id || ge.getFeatures().getChildNodes().item(i).getId() == "'" + id + "'")
{
id = ge.getFeatures().getChildNodes().item(i).getId();
ge.getFeatures().removeChild(ge.getElementById(id));
break;
}
}
The issue is that you can't re-add a feature using an ID that you have already used until all references to it have been released. This is usually done by the internal garbage collector - but you can also force it by calling release() on the object you are deleting. This ...
Permanently deletes an object, allowing its ID to be reused.
Attempting to access the object once it is released will result in an
error.
Also when an object is created with the API the object does not have a base address. In this case, the object can be returned by passing only its ID to getElementById(). This can then be used to remove the feature.
e.g.
function remove(id) {
ge.getElementById(id).release();
}
Really though I would look to avoid using IDs altogether and would simply keep a variable that points to the feature, then use that to remove. e.g.
function add(fileurl) {
var link = ge.createLink(''); //no id
link.setHref(fileurl);
var networkLink = ge.createNetworkLink(''); //no id
networkLink.set(link, true, true);
ge.getFeatures().appendChild(networkLink);
return networkLink;
}
var link1 = add("http://yoursite.com/file.kml");
var link2 = add("http://yoursite.com/file2.kml"); // etc...
// then to remove, simply...
link1.release();
link2.release();
OK. So I figured out that if you remove an object from GE, and then try to add another object with the same id, GE complains and won't create the object - unless some time (approx. 30 seconds in my case) has passed. This time actually is required by JavaScript to garbage collect the object.
Setting the object to null doesn't give immediate result but may help Garbage Collector.
Also release() method offered by GE does not help.
I'm trying to add a network link when someone clicks on a placemark that has been loaded via KML. What I do is attach an event handler to the globe and check to see if the user clicked on a placemark.
On the html, there is a button that when clicked, removes the network link from google earth when clicked (see trackRemoval). Everything seems to work the first time a placemark is clicked.
The problem is when the placemark is clicked a second time (after having the network link removed), the call to createNetworkLink fails. Attached is the relevant snippets of code.
Can someone see what I'm doing wrong?
var ge = new Array(2);
function clickHandler(event) {
if (event.getTarget().getType() == 'KmlPlacemark') {
event.preventDefault();
var placemark = event.getTarget();
var device = placemark.getName();
var networkLink = ge[0].createNetworkLink(device + "link");
var link = ge[0].createLink("");
networkLink.setDescription("Vechicle view for" + device);
networkLink.setName("Track for " + device);
networkLink.setFlyToView(true);
link.setHref("http://x.x.x.x/blah/blah.kml");
link.setRefreshMode(ge[0].REFRESH_ON_INTERVAL);
link.setRefreshInterval(60);
networkLink.setLink(link);
ge[0].getGlobe().getFeatures().appendChild(networkLink);
}
}
function initgeaor(instance) {
google.earth.addEventListener(instance.getGlobe(), 'click', clickHandler);
}
function trackRemoval() {
var device = this.name;
var networklink = ge[0].getElementById(device + "link");
ge[0].getGlobe().getFeatures().removeChild(networklink);
}
See https://developers.google.com/earth/documentation/reference/interface_kml_object
See the note on the method release(). From my experience this "indeterminate amount of time" is difficult to gauge. So if you remove an object from GE, and then try to add another object with the same id, GE complains and won't create the object - unless that 'indeterminate amount of time' has passed.
You may be able to use a timer after you removeChild() and release() the network link.
You could also give your new KmlNetworkLink a different id each time you create it so there's no collision. I'm not sure if this is an option for you. var networkLink = ge[0].createNetworkLink(device + "link" + idNumber);
I have created one dojo datagrid. Every column has a formatter attached to it. When grid is generated the formatter is called. Now I want it so that if a user selects any row the formatter will be called and some strings should be attached to the selected row's column element.
Like grid is like this :
COLUMN
-------
a
b
c
and now user selects the 2nd row, the grid should change to :
COLUMN
-------
a
b SELECTED
c
Currently I implemented it like this :
if(this.grid.selection.selectedIndex !== -1){
retrun value + "SELECTED";
}else{
return value;
}
Can you please suggest a some good way of doing this? Please note that "SELECTED" string should not be added to the grid store.
The formatted is not hooked into clicking / selection of rows. It is solely performed when the contents (value) of a cell is set. Instead you'd want to move focus over to onRowClicked - an event on the grid component. It works like this:
grid.onRowClick = onRowClickHandler;
I wouldnt know which of following samples would put you closest to your goal but onRowClickHandler could be setup as such:
function onRowClickHandler(evt) {
var rows = this.selection.getSelected();
// perform cell rendering here
dojo.forEach(rows, function(row) {
// this row is an item though.. you will have row._O as its index
});
}
OR
function onRowClickHandler(e) {
var cellClicked = this.focus.cell
cellClicked.formatter();
}
However you may find that there are not much references to the viewable data anywhere in the grid component.. You could use following query selectors to find cell data and update the viewed html by calling formatter on each value. You would need to capture a previous selection for 'teardown' of your custom setting of values though.
var prevSelectedRows = [];
function onRowClickHandler(evt) {
var idx = this.selection.selectedIndex,
rawRow = dojo.query(".dojoxGridRow:nth-child("+(idx+1)+")", this.domNode)[0],
self = this;
// perform resetting of viewable values
dojo.forEach(prevSelectedRows, function(raw) {
dojo.query('.dojoxGridCell', raw).forEach(function(cellDOM, i) {
cellDOM.innerHTML = cellDOM.innerHTML.replace("SELECTED", "");
});
});
prevSelectedRows = []; // reset prev selection
// look into grid.view.content for methods on this
// perform setting of viewable values (SELECTED)
dojo.query('.dojoxGridCell', rawRow).forEach(function(cellDOM, i) {
// this function might be of interest, lets see how it looks in console
console.log(self.layout.cells[i].formatter);
cellDOM.innerHTML = cellDOM.innerHTML + "SELECTED"
});
prevSelectedRows.push(rawRow);
}
So this is driving me crazy. I've got an advanced data grid with a dataprovider that's an array collection w/ hierarchical data. Each object (including the children) has an id field. I'm trying to drag and drop data from within the ADG. When this happens, I need to grab the id off the drop target and change the dragged object's parentid field. Here's what I've got:
public function topAccountsGrid_dragDropHandler(event:DragEvent):void{
//In this function, you need to make the move, update the field in salesforce, and refresh the salesforce data...
if(checkActivateAccountManageMode.selected == true) {
var dragObj:Array = event.dragSource.dataForFormat("treeDataGridItems") as Array;
var newParentId:String = event.currentTarget['Id'];
dragObj[0].ParentId = newParentId;
} else {
return;
}
app.wrapper.save(dragObj[0],
new mx.rpc.Responder(
function():void {
refreshData();
},
function():void{_status = "apex error!";}
)
);
}
I can access the data I'm draggin (hence changing parentId) but not the currentTarget. I think the hierarchical data is part of the problem, but I can't find much in the documentation? Any thoughts?
event.currentTarget is not a node, it's the ADG itself. However, it's quite easy to get the information you want, since the ADG stores that data internally (as mx_internal).
I'm using the following code snippets (tested with Flex SDK 4.1) in a dragOver handler, but I guess it will work in a dragDrop handler too.
protected function myGrid_dragDropHandler(event:DragEvent):void
{
// Get the dragged items. This could either be an Array, a Vector or NULL.
var draggedItems:Object = getDraggedItems(event.dragSource);
if (!draggedItems)
return;
// That's our ADG where the event handler is registered.
var dropTarget:AdvancedDataGrid = AdvancedDataGrid(event.currentTarget);
// Get the internal information about the dropTarget from the ADG.
var dropData:Object = mx_internal::dropTarget._dropData;
// In case the dataProvider is hierarchical, get the internal hierarchicalData aka rootModel.
var hierarchicalData:IHierarchicalData = dropTarget.mx_internal::_rootModel;
var targetParent:Object = null;
// If it's a hierarchical data structure and the dropData could be retrieved
// then get the parent node to which the draggedItems are going to be added.
if (hierarchicalData && dropData)
targetParent = dropData.parent;
for each (var draggedItem:Object in draggedItems)
{
// do something with the draggedItem
}
}
protected function getDraggedItems(dragSource:DragSource):Object
{
if (dragSource.hasFormat("treeDataGridItems"))
return dragSource.dataForFormat("treeDataGridItems") as Array;
if (dragSource.hasFormat("items"))
return dragSource.dataForFormat("items") as Array;
if (dragSource.hasFormat("itemsByIndex"))
return dragSource.dataForFormat("itemsByIndex") as Vector.<Object>;
return null;
}
var dropData:Object = mx_internal::dropTarget._dropData;
should be
var dropData:Object = dropTarget.mx_internal::_dropData;
Try this.