I am trying to draw several seperated Polygons (buildings) out of a database.
The data I get out of my database looks like this:
<buildings>
<building build_id="94" build_gebaeude="A"
build_geoX="49.26173942769648" build_geoY="7.350542675857582"
/>
<building build_id="95" build_gebaeude="A"
build_geoX="49.26173942769648" build_geoY="7.3524094933319475"
/>
<building build_id="96" build_gebaeude="A"
build_geoX="49.26019903253632" build_geoY="7.35234512031559"
/>
<building build_id="97" build_gebaeude="A"
build_geoX="49.26032506667364" build_geoY="7.350692879562416"
/>
<building build_id="98" build_gebaeude="B"
build_geoX="49.26155738350112" build_geoY="7.362129818801918"
/>
<building build_id="99" build_gebaeude="B"
build_geoX="49.26157138692462" build_geoY="7.364275586013832"
/>
<building build_id="100" build_gebaeude="B"
build_geoX="49.260255047748224" build_geoY="7.364361416702309"
/>
<building build_id="101" build_gebaeude="B"
build_geoX="49.260311062896506" build_geoY="7.362065445785561"
/>
</buildings>
The following code shows what I am doing to draw the buildings:
for (var i = 0; i < building.length-1; i++) {
var point = new google.maps.LatLng(
parseFloat(building[i].getAttribute("build_geoX")),
parseFloat(building[i].getAttribute("build_geoY"))
);
latlngbounds.extend(point);
if( building[i].getAttribute("build_gebaeude") ==
building[i+1].getAttribute("build_gebaeude") )
{
path.push(point);
}
else {
path.push(point);
poly = new google.maps.Polygon({
paths: path,
strokeWeight: 5,
strokeOpacity: 1,
fillOpacity: 0.30,
strokeColor:'#FFFFFF',
fillColor: '#a3141d'
});
polygons.push(poly);
path.clear();
}
}
polygons[0].setMap(map);
polygons[1].setMap(map);
The problem is that not all points are drawn? I donĀ“t understand where the problem is?
It looks like there may be a problem with the paths array. It's not clear from the code you have included whether paths is a JavaScript Array or an array-like object, but since you are working with paths within the for loop and then attempting to clear the paths within the loop, I suggest:
Create a new paths Array within the loop as it is needed
Add code to manage the state of the array looping
Change the code you use to check the building attributes and testing equality
Since you are passing the paths array to the Polygon constructor, don't clear the array
var paths = null;
var aNewPathIsNeeded = true;
for ( var i = 0; i < building.length-1; i++) {
//Create your point
//Extend your bounds
//Since you always add the point to the paths array, move it out of the if
//check and perform some array state management:
if ( aNewPathIsNeeded ) {
paths = new Array( point );
aNewPathIsNeeded = false;
}
else { paths.push( point ); }
//Change the if condition to deal with just the specific case of interest:
var currentBldgAttr = building[i].getAttribute("build_gebaeude");
var nextBldgAttr = building[i+1].getAttribute("build_gebaeude");
if ( !( currentBldgAttr === nextBldgAttr ) ) { //Change to use: '==='
//Create your polygon
//Add the polygon to the polygons array
//New code to manage paths state:
aNewPathIsNeeded = true;
}
}
I'm not sure exactly what your problem is, but I believe this code will help you move forward. As a side note, clear() is not a JavaScript Array method; when you want to empty an Array, use: array.length = 0 as described in: Is JavaScript's array.clear() not a function?
Related
According to my understanding, project.getItems({selected: true}) returns wrong results: I'm selecting a curve, it returns the parent Path: Sketch
Try clicking on a curve or a segment. Whole path will be moved. Then try changing the behavior by setting var workaround = false to var workaround = true to observe desired behavior.
How can I get exactly what is really selected?
Current workaround
I'm currently adding those objects into an array on selection and use those items instead of project.getItems({selected: true}).
The thing is that in Paper.js architecture, curves and segments are not items (they are part of a specific item which is the path). So you shouldn't expect project.getItems() to return anything else than items.
Another thing you have to know is that a path is assumed selected if any of its part is selected (curves, segments, points, handles, position, bounds, ...). And a curve is assumed selected if all of its parts are selected (points and handles).
With that in mind, you can create an algorithm to retrieve "what is really selected" based on project.getItems({selected: true}) as its first part. Then, you need to loop through curves and segments to check if they are selected.
Here is a sketch demonstrating a possible solution.
var vector = new Point(10, 10);
// Create path.
var path = new Path({
segments: [
[100, 100],
[200, 100],
[260, 170],
[360, 170],
[420, 250]
],
strokeColor: 'red',
strokeWidth: 10
});
// Translate given thing along global vector.
function translateThing(thing) {
switch (thing.getClassName()) {
case 'Path':
thing.position += vector;
break;
case 'Curve':
thing.segment1.point += vector;
thing.segment2.point += vector;
break;
case 'Segment':
thing.point += vector;
break;
}
}
// On mouse down...
function onMouseDown(event) {
// ...only select what was clicked.
path.selected = false;
hit = paper.project.hitTest(event.point);
if (hit && hit.location) {
hit.location.curve.selected = true;
}
else if (hit && hit.segment) {
hit.segment.selected = true;
}
// We check all items for demo purpose.
// Move all selected things.
// First get selected items in active layer...
project.activeLayer.getItems({ selected: true })
// ...then map them to what is really selected...
.map(getSelectedThing)
// ...then translate them.
.forEach(translateThing);
}
// This method returns what is really selected in a given item.
// Here we assume that only one thing can be selected at the same time.
// Returned thing can be either a Curve, a Segment or an Item.
function getSelectedThing(item) {
// Only check curves and segments if item is a path.
if (item.getClassName() === 'Path') {
// Check curves.
for (var i = 0, l = item.curves.length; i < l; i++) {
if (item.curves[i].selected) {
return item.curves[i];
}
}
// Check segments.
for (var i = 0, l = item.segments.length; i < l; i++) {
if (item.segments[i].selected) {
return item.segments[i];
}
}
}
// return item by default.
return item;
}
That said, depending on your real use case, your current workaround could be more appropriate than this approach.
I'm looking for a way to use framecloud type popup with my current setup. Unfortunately all my attempts have either not worked or will only work on the most recently placed maker.
In the course of trying to get it to work I have converted my original script from using Markers to using Vectors to placing the marker points (as I've seen that it's easier to customize vectors than markers.)
Now which ever one I can get to work I'll use, but after working on this for a few days I'm at my wits end and need a helping hand in the right direction.
My points are pulled from a google spreadsheet using tabletop.js. The feature is working how I wish it to, with the markers being placed on their respective layer based on a field I called 'type'.
While I have a feeling that might have been the source of my problem with the Markers type layer, I'm not sure how to fix it.
You can view the coding through these pages
(Links removed due to location change.)
Thanks for all help in advance.
I finally got it to work. For anyone in a similar situation here's my final code for the layers. I did change the names of the layers from what they are originally and blacked out the spreadsheet I used, but the changes should be noticeable.
//
//// Set 'Markers'
//
var iconMarker = {externalGraphic: 'http://www.openlayers.org/dev/img/marker.png', graphicHeight: 21, graphicWidth: 16};
var iconGeo = {externalGraphic: './images/fortress.jpg', graphicHeight: 25, graphicWidth: 25};
var iconAero = {externalGraphic: './images/aeropolae.jpg', graphicHeight: 25, graphicWidth: 25}; // Image is the creation of DriveByArtist: http://drivebyartist.deviantart.com/
var vector1 = new OpenLayers.Layer.Vector("1");
var vector2 = new OpenLayers.Layer.Vector("2");
var vector3 = new OpenLayers.Layer.Vector("3");
// Pulls map info from Spreadsheet
//*
Tabletop.init({
key: 'http://xxxxxxxxxx', //Spreadsheet URL goes here
callback: function(data, tabletop) {
var i,
dataLength = data.length;
for (i=0; i<dataLength; i++) { //following are variables from the spreadsheet
locName = data[i].name;
locLon = data[i].long;
locLat = data[i].lat;
locInfo = data[i].info;
locType = data[i].type; // Contains the following string in the cell, which provides a pre-determined output based on provided information in the spreadsheet: =ARRAYFORMULA("<h2>"&B2:B&"</h2><b>"&G2:G&"</b><br /> "&C2:C&", "&D2:D&"<br />"&E2:E&if(ISTEXT(F2:F),"<br /><a target='_blank' href='"&F2:F&"'>Read More...</a>",""))
locLonLat= new OpenLayers.Geometry.Point(locLon, locLat);
switch(locType)
{
case "Geopolae":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconGeo);
vector1.addFeatures(feature);
break;
case "POI":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconMarker);
vector2.addFeatures(feature);
break;
case "Aeropolae":
feature = new OpenLayers.Feature.Vector(
locLonLat,
{description:locInfo},
iconAero);
vector3.addFeatures(feature);
break;
}
}
},
simpleSheet: true
});
map.addLayers([vector1, vector2, vector3]);
map.addControl(new OpenLayers.Control.LayerSwitcher());
//Add a selector control to the vectorLayer with popup functions
var controls = {
selector: new OpenLayers.Control.SelectFeature(Array(vector1, vector2, vector3), { onSelect: createPopup, onUnselect: destroyPopup })
};
function createPopup(feature) {
feature.popup = new OpenLayers.Popup.FramedCloud("pop",
feature.geometry.getBounds().getCenterLonLat(),
null,
'<div class="markerContent">'+feature.attributes.description+'</div>',
null,
true,
function() { controls['selector'].unselectAll(); }
);
feature.popup.autoSize = true;
feature.popup.minSize = new OpenLayers.Size(400,100);
feature.popup.maxSize = new OpenLayers.Size(400,800);
feature.popup.fixedRelativePosition = true;
feature.popup.overflow ="auto";
//feature.popup.closeOnMove = true;
map.addPopup(feature.popup);
}
function destroyPopup(feature) {
feature.popup.destroy();
feature.popup = null;
}
map.addControl(controls['selector']);
controls['selector'].activate();
}
I am trying to take a string which has shape option information and create the shape on my Google Map application.
The string is made by splitting an array that was built from a local text document.
The string appears as:
Circle{center: new google.maps.LatLng(38.041872419557094, -87.6046371459961),radius:5197.017394363823,fillColor: '#000000',strokeWeight: 1,strokeColor: '#000000',map:map};
The function I have to take such string and make the shape appears as:
function loadDrawings(evt)
{
var f = evt.target.files[0];
if (!f)
{
alert("Failed to load file");
}
else if (!f.type.match('text.*'))
{
alert(f.name + " is not a valid text file.");
}
else
{
var r = new FileReader();
r.onload = function (e)
{
var contents = e.target.result;
var drawings = [];
var drawing;
var drawingType;
var shape;
var shapeOptions;
drawings = contents.split(";");
for (i = 0; i < drawings.length - 1; i++) {
drawing = drawings[i].toString();
drawingType = drawing.substr(0, drawing.indexOf('{'));
if (drawingType == "Circle")
{
shapeOptions = drawing.substr(6); //UNIQUE TO CIRCLE
shape = new google.maps.Circle(shapeOptions);
shape.setMap(map);
}
};
}
r.readAsText(f);
}
}
My issue is shapeOptions as a string does not work in the above syntax for creating the Circle. However, if I take the contents of the string, which is:
{center: new google.maps.LatLng(38.041872419557094, -87.6046371459961),radius:5197.017394363823,fillColor: '#000000',strokeWeight: 1,strokeColor: '#000000',map:map}
And directly enter it, the shape appears.
Do I need a certain variable type for my shapeOptions for this to work? I know that the new google.maps. requires (), but I have had no luck creating a variable from my string. Am I missing something here?
Much appreciation for any help!
Your shapeOptions string is a JavaScript object literal, so you can eval() it to get the object:
shapeOptions = eval( '(' + drawing.substr(6) + ')' );
Since it has map:map in it, you don't need the subsequent setMap() call.
Also, you're missing a var for the i variable. I don't really recommend the coding style where all the var statements go at the top of a function. I find it error-prone; it's too easy to omit a var without noticing it. (I know some famous JavaScript experts insist that var at the top is the only way to do it, but they fail to see the tradeoffs involved.)
You don't need the .toString() on drawings[i]. It's already a string.
You have two different brace styles. Best to pick one and stick with it. For JavaScript, putting the { on a line by itself is not recommended, because this code will not do what you expect:
return // hoping to return an object literal - but it doesn't!
{
a: 'b',
c: 'd'
}
Whereas this code does work correctly:
return {
a: 'b',
c: 'd'
}
Since you are using FileReader, I think it's safe to assume you also have .forEach() available.
You can replace the code that uses .indexOf() and the hard coded length with a regular expression.
Putting all that together, you might end up with code like this:
var r = new FileReader();
r.onload = function( e ) {
e.target.result.split(";").forEach( function( drawing ) {
var match = drawing.match( /^(\w+)({.*})$/ );
if( ! match ) return; // unrecognized
var type = match[0], options = eval( match[1] );
switch( type ) {
case "Circle":
new google.maps.Circle( options );
break;
}
});
}
r.readAsText( f );
But you may be able to take it a step further. So far we're looking at a Circle (line breaks added for readability):
Circle{
center: new google.maps.LatLng(
38.041872419557094,
-87.6046371459961
),
radius:5197.017394363823,
fillColor: '#000000',
strokeWeight: 1,
strokeColor: '#000000',
map:map
}
With only a simple change, that could be executed as JavaScript directly. You just need the 'new google.maps.' at the beginning and () around the object literal:
new google.maps.Circle({
center: new google.maps.LatLng(
38.041872419557094,
-87.6046371459961
),
radius:5197.017394363823,
fillColor: '#000000',
strokeWeight: 1,
strokeColor: '#000000',
map:map
})
I assume you will have other drawing types as well? Will they all map directly to google.maps.* objects like Circle does? If so, you could simply do:
var r = new FileReader();
r.onload = function( e ) {
e.target.result.split(";").forEach( function( drawing ) {
eval( drawing.replace(
/^(\w+)({.*})$/,
'new google.maps.$1(\$2)'
) );
});
}
r.readAsText( f );
I have a OpenLayers.Feature.Vector created as follows:
var multiPol = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon([polygonGeometry1,polygonGeometry2]));
Both polygons represent same logical object (an "entity") on a map, say a cloud. This is why I keep them in one feature.
I would like to draw it so that each component of this multi-polygon (polygonGeometry1, polygonGeometry2) is drawn with different color when it's added to a layer:
var layer = new OpenLayers.Layer.Vector("polygonLayer");
layer.addFeatures([multiPol]);
I have taken a look at styles, style maps and rules in OpenLayers but they appear to be insufficient. They do enable me to draw each geometry type with different color but only if they belong to different features (vectors). Is there a way to solve this problem? Do really I have to use separate Vector for each polygon?
The proposed solution by Jon Snyder provided us with a general idea, but did not fully work for in the end (especially extending OpenLayers.Layer.Vector wasn't necessary for this task, based on OpenLayers 2.1x).
We created a class YourApp.Handler.EndPointsPath (extending OpenLayers.Handler.Path), where the function geometryClone() returns a new geometry of type YourApp.Geometry.EndPointsPath.
We then patched the function drawGeometry() in OpenLayers.Renderer.Elements to draw this new geometry:
OpenLayers.Util.extend(OpenLayers.Renderer.Elements.prototype, {
drawGeometry: function (geometry, style, featureId) {
var cl = geometry.CLASS_NAME;
var rendered = true,
i, len;
if ((cl === "OpenLayers.Geometry.Collection") ||
(cl === "OpenLayers.Geometry.MultiPoint") ||
(cl === "OpenLayers.Geometry.MultiLineString") ||
(cl === "OpenLayers.Geometry.MultiPolygon") ||
(cl === "YourApp.Geometry.EndPointsPath")) {
// Iterate over all Geometry components and draw each individually
for (i = 0, len = geometry.components.length; i < len; i++) {
// Is there a style for each of the components?
if (OpenLayers.Util.isArray(style)) {
// Draw Geometry with own style
rendered = this.drawGeometry(geometry.components[i], style[i], featureId) && rendered;
} else {
// Draw Geometry with common style
rendered = this.drawGeometry(geometry.components[i], style, featureId) && rendered;
}
}
return rendered;
}
// (...standard code...)
},
eraseGeometry: function (geometry, featureId) {
var cl = geometry.CLASS_NAME,
i, len;
if ((cl === "OpenLayers.Geometry.MultiPoint") ||
(cl === "OpenLayers.Geometry.MultiLineString") ||
(cl === "OpenLayers.Geometry.MultiPolygon") ||
(cl === "YourApp.Geometry.EndPointsPath") ||
(cl === "OpenLayers.Geometry.Collection")) {
for (i = 0, len = geometry.components.length; i < len; i++) {
this.eraseGeometry(geometry.components[i], featureId);
}
// (...standard code...)
}
}
});
As far as I can tell, to get this functionality you will need to extend the classes with your own.
First create an extension to OpenLayers.Feature.Vector, name it YourApp.Feature.MultiVector. You can see examples on how to extend classes by looking at the OpenLayers code. This class should accept an array of styles and the multiPolygon. It should have a method that will return a list of OpenLayers.Feature.Vectors each with their own style.
Second create an extension to OpenLayers.Layer.Vector, name it YourApp.Layer.VectorSupportingMultiStyledFeatures. You will need to override the "drawFeature" method. In the drawFeature method test to see if the type of the feature is a MultiVector. If it is, loop through each feature in the MultiVector and call renderer.drawFeature(feature). Otherwise call the super.drawFeature method.
So your code to call it would look like this:
var multiPol = new YourApp.Feature.MultiVector(
new OpenLayers.Geometry.MultiPolygon([polygonGeometry1,polygonGeometry2]),
[style1,style2,style3,style4]);
var layer = new YourApp.Layer.VectorSupportingMultiStyledFeatures("polygonLayer");
layer.addFeatures([multiPol]);
Is it possible to sort an XMLList? All the examples I can find on it create a new XMLListCollection like this:
MyXMLListCol = new XMLListCollection(MyXMLList);
I don't think the XMLListCollection in this case has any reference to the XMLList so sorting it would leave my XMLList unsorted, is this correct?
How can I sort the XMLList directly?
Thanks
~Mike
So I finally got my search terms altered enough I actually churned up an answer to this.
Using the technique I got from here:
http://freerpad.blogspot.com/2007/07/more-hierarchical-sorting-e4x-xml-for.html
I was able to come up with this:
public function sortXMLListByAttribute(parentNode:XML,xList:XMLList,attr:String):void{
//attr values must be ints
var xListItems:int = xList.length();
if(xListItems !=0){
var sortingArray:Array = new Array();
var sortAttr:Number = new Number();
for each (var item:XML in xList){
sortAttr = Number(item.attribute(attr));
if(sortingArray.indexOf(sortAttr)==-1){
sortingArray.push(sortAttr);
}
//piggy back the removal, just have to remove all of one localName without touching items of other localNames
delete parentNode.child(item.localName())[0];
}
if( sortingArray.length > 1 ) {
sortingArray.sort(Array.NUMERIC);
}
var sortedList:XMLList = new XMLList();
for each(var sortedAttr:Number in sortingArray){
for each (var item2:XML in xList){
var tempVar:Number = Number(item2.attribute(attr));
if(tempVar == sortedAttr){
sortedList += item2
}
}
}
for each(var item3:XML in sortedList){
parentNode.appendChild(item3);
}
}
}
Works pretty fast and keeps my original XML variable updated. I know I may be reinventing the wheel just to not use an XMLListCollection, but I think the ability to sort XML and XMLLists can be pretty important
While there is no native equivalent to the Array.sortOn function, it is trivial enough to implement your own sorting algorithm:
// Bubble sort.
// always initialize variables -- it save memory.
var ordered:Boolean = false;
var l:int = xmlList.length();
var i:int = 0;
var curr:XML = null;
var plus:XML = null;
while( !ordered )
{
// Assume that the order is correct
ordered = true;
for( i = 0; i < l; i++ )
{
curr = xmlList[ i ];
plus = xmlList[ i + 1 ];
// If the order is incorrect, swap and set ordered to false.
if( Number( curr.#order ) < Number( plus.#order ) )
{
xmlList[ i ] = plus;
xmlList[ i + 1 ] = curr;
ordered = false;
}
}
}
but, realistically, it is far easier and less buggy to use XMLListCollection. Further, if someone else is reading your code, they will find it easier to understand. Please do yourself a favor and avoid re-inventing the wheel on this.