Google Maps API.
I am looking for the best way to display simple dot icons (with changeable colors) that do not resize on zooming.
Instead when zoomed to a certain level, the simple dot icons disappear and a summary icon appears with a number value of each color appears.
I have looked into Marker Icons, but they resize on zoom.
To clarify- The marker resizes in reference to the actual latitude and longitude measurements. It remains the same size in reference to the map screen size.
I am looking for a way for the small dot to remain the same size in reference to the latitude and longitude. I have looked into the circle class and those do not appear to be perfect circles on the map. They have rough edges.
Pictures Below
1.Zoomed In All the way
2. Zoomed Out - Circles stay same size in reference to the Lat and Lon
3a. Zoomed Out - Circles disappear and "Summary circles" appear
3b. Zoomed Out - Circles disappear and Heat Map appears
New Code I've Tried
<script type="text/javascript">
var map = null;
var spotsArray = [];
window.onload = initialize();
function initialize() {
map = new google.maps.Map(document.getElementById("map-canvas")
, {
mapTypeId: google.maps.MapTypeId.roadmap
, zoom: #Model.StartingZoom
, disableDefaultUI: true
, zoomControl: true
, center: new google.maps.LatLng(#Model.StartingLocation)
});
map.addListener('zoom_changed', zoomChange, this);
dataLayer = new google.maps.Data({
map: map
});
window.setInterval(function () { UpdateData() }, 5000);
}
function getScaleSize() {
var scale = null;
var mapZoom = map.getZoom();
switch (mapZoom) {
case 15:
case 16:
case 17:
scale = 1;
break;
case 18:
scale = 10;
break;
case 19:
case 20:
case 21:
case 22:
scale = 20;
break;
default:
scale = 0;
}
return scale;
}
function zoomChange() {
for (var nIndex = 0; nIndex < spotsArray.length; nIndex++) {
var Spot = spotsArray[nIndex].Spot;
var Scale = getScaleSize();
Spot.icon.scale = Scale;
}
}
function UpdateData() {
GetJsonData('#Url.Action("GetMapData")'
,{}
,function(jsonData)
{
$(jsonData).each(function (item)
{ AddUpdateSpotOnMap(jsonData[item]) });
});
};
function AddUpdateSpotOnMap(SpotData) {
//SpotID, Latitude, Longitude, StrokeColor, StrokeOpacity, StrokeWeight, FillColor, FillOpacity
//Check if in Array
var inArrayIndex = NaN;
for (var nIndex = 0; nIndex < spotsArray.length; nIndex++) {
if (SpotData.spotID == spotsArray[nIndex].SpotID) {
inArrayIndex = nIndex;
break;
}
}
if (isNaN(inArrayIndex) == false) {
//Update Color
spotsArray[inArrayIndex].Spot.setOptions({ StrokeColor: SpotData.strokeColor, StrokeOpacity: SpotData.strokeOpacity, StrokeWeight: SpotData.strokeWeight, FillColor: SpotData.fillColor, FillOpacity: SpotData.fillOpacity });
}
else {
var Spot = new google.maps.Marker(
{
position: new google.maps.LatLng(SpotData.latitude, SpotData.longitude),
map: map,
title: SpotData.hoverOver,
icon: {
path: google.maps.SymbolPath.CIRCLE,
strokeColor: SpotData.strokeColor,
strokeOpacity: SpotData.strokeOpacity,
strokeWeight: SpotData.strokeWeight,
fillColor: SpotData.fillColor,
fillOpacity: SpotData.fillOpacity,
scale: 5 }
});
spotsArray.push({ SpotID: SpotData.spotID, Spot: Spot });
dataLayer.add(Spot);
}
};
</script>
I didn't get your question. But If you are looking for dynamic styling this might help you.
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Google Maps</title>
<style type="text/css">
#map {
height: 100%;
width: 100%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
var map = null;
var dataLayer = null;
function init() {
map = new google.maps.Map(document.getElementById('map'), {
center : {
lat : 12.931,
lng : 77.59180
},
zoom : 1
});
map.addListener('zoom_changed', zoomChange, this);
dataLayer = new google.maps.Data({
map : map
});
dataLayer.setStyle(styleFunction);
dataLayer.loadGeoJson('yourJsonFilePath.json');
}
function styleFunction(feature) {
if (feature.getGeometry()) {
var fillColor = null;
var scale = null;
switch (map.getZoom()) {
case 0:
case 1:
case 2:
case 3:
fillColor = "#0000ff";
scale = 40;
break;
case 4:
case 5:
case 6:
case 7:
fillColor = "#00ff00";
scale = 30;
break;
case 8:
case 9:
case 10:
case 11:
fillColor = "#ff0000";
scale = 20;
break;
case 12:
case 13:
case 14:
case 15:
fillColor = "#ff00ff";
scale = 10;
break;
default:
fillColor = "#ffff00";
scale = 5;
}
return {
icon : {
path : google.maps.SymbolPath.CIRCLE,
fillColor : fillColor,
fillOpacity : 1,
strokeWeight : 1,
strokeColor : fillColor,
scale : scale
}
}
}
}
function zoomChange(event) {
dataLayer.setStyle(styleFunction);
}
</script>
<script
src="https://maps.googleapis.com/maps/api/js?v=3.26&key=APIKey&callback=init"></script>
EDIT
In the style function instead of hardcoding the fill color you can get it from the feature property if you have in the json data as below
function styleFunction(feature) {
if (feature.getGeometry()) {
var fillColor = feature.getProperty("color");
var scale = null;
switch (map.getZoom()) {
case 0:
case 1:
case 2:
case 3:
scale = 40;
break;
case 4:
case 5:
case 6:
case 7:
scale = 30;
break;
case 8:
case 9:
case 10:
case 11:
scale = 20;
break;
case 12:
case 13:
case 14:
case 15:
scale = 10;
break;
default:
scale = 5;
}
return {
icon : {
path : google.maps.SymbolPath.CIRCLE,
fillColor : fillColor,
fillOpacity : 1,
strokeWeight : 1,
strokeColor : fillColor,
scale : scale
}
}
}
}
Here color is the property which will be in each feature
Related
Hey guys I have a slight problem here I have a Konva.js app which is working pretty well. I have this function called SET_STAGE_DATA that is supposed to save the images current position on the canvas so it can be redrawn when the canvas disapears. It works but when you scale the image from the left the image shifts. I am not sure how to fix this. I thought I may be able to fix it with offset but to no avail.
Heres my SET_STAGE_DATA function
[SET_STAGE_DATA](state){
const isStage = state.stage;
if(isStage){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];;
let i = g.findOne("Image").getAttrs();
let position = g.findOne("Image").position();
console.log("this is position", position);
let group = { x: g.getX(), y: g.getY() };
let image = { x: i.width, y: i.height, position };
let obj = { image, group };
stageData.push(obj);
}
I use the stageData just before I build the image like so
let groupPos;
let imagePos;
if(state.stageData){
console.log(true);
for(let i =0; i<state.stageData.length; i++){
imageObj.width = state.stageData[i].image.x;
imageObj.height = state.stageData[i].image.y;
imagePos = state.stageData[i].position;
groupPos = state.stageData[i].group;
}
} else {
groupPos = {
x: state.stage.width() / 2 - imageObj.width / 2,
y: state.stage.height() / 2 - imageObj.height / 2
};
}
console.log("data", {groupPos, image: {width: imageObj.width, height: imageObj.height}});
const image = new Konva.Image({
image: imageObj,
width: imageObj.width,
height: imageObj.height,
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
if(imagePos){
image.position(imagePos)
}
const group = new Konva.Group({
draggable: true,
x: groupPos.x,
y: groupPos.y
});
I also have the typical update function found in the documentation:
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
// image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
state.layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function(evt) {
group.draggable(true);
commit(SET_STAGE_DATA);
dispatch(UPDATE_ANIMATION, state.selectedNode);
state.layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
document.body.style.cursor = "pointer";
this.strokeWidth(4);
state.layer.draw();
});
anchor.on("mouseout", function() {
document.body.style.cursor = "default";
this.strokeWidth(2);
state.layer.draw();
});
group.add(anchor);
}
state.layer.draw();
},
when I scale to the left the image goes outside of the anchors on redraw. I know I can fix this visually as long as I don't redraw the image by doing image.position(topLeft.position()); as you can see is commented out but it always acts as if positioning isn't set if you drag from the left. If I scale from the bottom right everything is fine. it acts as though it goes back to the previous position of the images left top corrner but from what I understand using stage.find() will give you the current stage. I'm at a totally loss.
See in the image how it does not stay in the box. Any help here would be great thanks.
[1]: https://i.stack.imgur.com/HEfHP.jpg
Update the SET_STAGE_DATA function to look like this
[SET_STAGE_DATA](state){
const isStage = state.stage;
const isEditorMode = state.editorMode;
if(isStage && !isEditorMode){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];
let i = g.findOne("Image").getAttrs();
let group = {x: g.getX() + i.x, y: g.getY() + i.y };
let image = i;
let obj = { image, group };
stageData.push(obj);
}
state.stageData = stageData;
} else {
state.stageData = null;
}
}
group.x and y don't update on scale but image.x and y do. On move image x and y are 0 and if you add them to the group.x and y it will put the anchors where you need them to be.
I have a list of Vectors which represent points on three different floors in three.js.
I am trying to group these vectors according to the floor they belong to. Is there a good formula to do this? Perhaps find height from on vector or something. Not sure how to go about this. Any help would be appreciated.
Thanks,
Rob
As Wilt said... Can't really help you without more info.
Still, if your floors are even and all stand on the xz plane (in my example), You may indeed check the points' height (position.y) against the floors'.
var container, renderer, scene, camera, controls;
var floors = [];
var points = [], materials = [], heights = [];
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera + controls
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 50, 750);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
//floors
for(i=0; i<3; i++) {
var planeGeometry = new THREE.BoxGeometry(500, 500, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity : 0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = Math.PI / 2;
//plane.rotation.y = Math.PI / 8; //Uncomment to see this doesn't work if the floors move, i.e. changing rotation/position. If this is what you need, just raycast from point to floor in the animation loop and count how many floors the ray goes through (intersects.length)
plane.position.y = 75*i;
heights.push(plane.position.y);
floors.push(plane);
scene.add(plane);
}
//wall
var height = heights[2];
var planeGeometry = new THREE.BoxGeometry(500, height+100, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity:0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.position.y = heights[1]+45;
plane.position.z = -510/2;
scene.add(plane);
// points
for (i=0; i<200; i++) {
var sphereGeometry = new THREE.SphereGeometry(3, 32, 16);
var sphere = new THREE.Mesh(sphereGeometry);
sphere.position.x = Math.random() * 500 - 250;
sphere.position.y = Math.random() * 300 - 100;
sphere.position.z = Math.random() * 500 - 250;
scene.add(sphere);
points.push(sphere);
}
// events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function addSpheres() {
//addSpheres
for (i=0;i<200;i++) {
var that = points[i].position.y;
points[i].position.y = ( that < heights[0] ) ? 200 : that - 0.5;
if ( that > heights[0] && that < heights[1] ) points[i].material = materials[0];
if ( that < heights[2] && that > heights[1] ) points[i].material = materials[1];
if ( that > heights[2] ) points[i].material = materials[2];
points[i].material.needsUpdate = true;
}
}
function animate() {
controls.update();
addSpheres();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
Now, feel free to "group" these points according to your needs.
Please note that "vectors" are different from "points". Read more about the difference.
Raycasting would be the way to go if you had a more complex scene (moving floors/different planes, points moving in different directions).
I have a curve running above or below a sloped line and I want to fill the area just above the line and below the curve, but not the area below the line and above the curve, as illustrated in the left image.
Again, it is easy to fill like right image, but I want to fill like in left image. So, how to get rid of the unwanted fill?
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
Okay, this is something I came up with for me in case anybody cares about it.
var slope = (dp.rightY - dp.leftY) / (dp.rightX - dp.leftX); // slope of the line
var pp = [];
pp.push({ x: dp.leftX, y: dp.leftY });
var wasAbove = true; // track if it's above the line
data.forEach(function (d) {
if (d.x >= dp.leftX && d.x <= dp.rightX) {
var yAtLine = (d.x - dp.leftX) * slope + dp.leftY;
if (d.y > yAtLine) {
if (!wasAbove)
pp.push({ x: d.x, y: yAtLine });
pp.push(d);
wasAbove = true;
} else if (wasAbove) {
pp.push({ x: d.x, y: yAtLine });
wasAbove = false;
}
}
});
pp.push({ x: dp.rightX, y: dp.rightY });
var peak = d3.svg.line().x(function (d) { return xScale(d.x) }).y(function (d) { return yScale(d.y) });
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
I want to know how to show infowindow on polyline in using Google Maps Api V3? and to appear in the middle of the polyline ?!
Firstly you will need to calculate the middle/center of the polyline. This has been discussed and answered here;
https://stackoverflow.com/a/9090409/787921
Then you will have to open the infowindow;
var infowindow = new google.maps.InfoWindow({
content: "infowindow text content"});
infowindow.setPosition(midLatLng);
infowindow.open(map);
find the middle point and set your custom view .
func showPath(polyStr :String){
polyline?.map = nil
mapView1.reloadInputViews()
pathDraw = GMSPath(fromEncodedPath: polyStr)!
polyline = GMSPolyline(path: pathDraw)
polyline?.strokeWidth = 4.0
polyline?.strokeColor = UIColor.init(red: 247/255.0, green: 55/255.0, blue: 76/255.0, alpha: 1.0)
polyline?.map = mapView1
let poinsCount = pathDraw.count()
let midpoint = pathDraw.coordinate(at: poinsCount)
DispatchQueue.main.async
{
self.addMarkerPin(corrdinate: midCordinate, distance: "10 min")
}
}
func addMarkerPin(corrdinate:CLLocationCoordinate2D, distance: String)
{
let marker = GMSMarker()
marker.position = corrdinate
PathTimeView = PathInfoView.loadFromNib() //here i am load Xib file, you can use your custom view
let DynamicView=PathTimeView
DynamicView.timelbl.text = distance
UIGraphicsBeginImageContextWithOptions(DynamicView.frame.size, false, UIScreen.main.scale)
DynamicView.layer.render(in: UIGraphicsGetCurrentContext()!)
let imageConverted: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
marker.icon = imageConverted
marker.map = self.mapView1
marker.infoWindowAnchor = CGPoint(x: -1900 , y: -2000)
}
First you should got center/middle of polyline and this what works for me
private fun centerPos(points: MutableList<LatLng>): LatLng {
val middleDistance = SphericalUtil.computeLength(points).div(2)
return extrapolate(points, points.first(), middleDistance.toFloat()) ?: points[0]
}
private fun extrapolate(path: List<LatLng>, origin: LatLng, distance: Float): LatLng? {
var extrapolated: LatLng? = null
if (!PolyUtil.isLocationOnPath(
origin,
path,
false,
1.0
)
) { // If the location is not on path non geodesic, 1 meter tolerance
return null
}
var accDistance = 0f
var foundStart = false
val segment: MutableList<LatLng> = ArrayList()
for (i in 0 until path.size - 1) {
val segmentStart = path[i]
val segmentEnd = path[i + 1]
segment.clear()
segment.add(segmentStart)
segment.add(segmentEnd)
var currentDistance = 0.0
if (!foundStart) {
if (PolyUtil.isLocationOnPath(origin, segment, false, 1.0)) {
foundStart = true
currentDistance = SphericalUtil.computeDistanceBetween(origin, segmentEnd)
if (currentDistance > distance) {
val heading = SphericalUtil.computeHeading(origin, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
origin,
(distance - accDistance).toDouble(),
heading
)
break
}
}
} else {
currentDistance = SphericalUtil.computeDistanceBetween(segmentStart, segmentEnd)
if (currentDistance + accDistance > distance) {
val heading = SphericalUtil.computeHeading(segmentStart, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
segmentStart,
(distance - accDistance).toDouble(),
heading
)
break
}
}
accDistance += currentDistance.toFloat()
}
return extrapolated
}
then You can add infoWindow with normal way with your platform at it is differ from each platform
With google maps api2 I was drawing a circle using this code:
var markerPoint = currentMarker.getPoint();
var polyPoints = Array();
var mapNormalProj = G_NORMAL_MAP.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPixel(markerPoint, mapZoom);
var polySmallRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var polyRadius = polySmallRadius;
var pixelX = clickedPixel.x + 5 + polyRadius * Math.cos(aRad);
var pixelY = clickedPixel.y - 10 + polyRadius * Math.sin(aRad);
var polyPixel = new GPoint(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPixelToLatLng(polyPixel,mapZoom);
polyPoints.push(polyPoint);
}
// Using GPolygon(points, strokeColor?, strokeWeight?, strokeOpacity?, fillColor?, fillOpacity?)
highlightCircle = new GPolygon(polyPoints,"#000000",2,0.0,"#FF0000",.5);
map.addOverlay(highlightCircle);
I've managed to transform this code to api3:
var markerPoint = currentMarker.getPosition();
var polyPoints = Array();
var mapNormalProj = map.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPoint(markerPoint);
var polyRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var pixelX = clickedPixel.x + 5 + (polyRadius * Math.cos(aRad));
var pixelY = clickedPixel.y - 10 + (polyRadius * Math.sin(aRad));
var polyPixel = new google.maps.Point(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPointToLatLng(polyPixel);
polyPoints.push(polyPoint);
}
highlightCircle = new google.maps.Polygon({
paths: polyPoints,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
highlightCircle.setMap(map);
If you look more closely at the api3 example, the mapZoom variable is not used anywhere.
In api2, the code generates a small circle around my marker - around 35px radius. When I zoom into the map, the radius stays at 35px (because the zoom is taken into account).
With api3 on the other hand, I have a huge circle - more than 200px wide and when I zoom in, the circle becomes bigger and bigger.
It behaves the same way as the circle object available in api3.
What I want is just a small circle around my marker, that is not 100km in diameter, but just a few pixels around my marker (this circle acts like a hover element in html).
Any ideas how to achieve that?
You might have better luck using custom marker, not a circle. See "Vector Icon" from the documentation here: https://developers.google.com/maps/documentation/javascript/overlays#Icons
var marker = new google.maps.Marker({
position: new google.maps.LatLng(-25.363882, 131.044922),
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10
},
draggable: true,
map: map
});
You're doing the calculation based on the Point plane which plane remains the same no matter what zoom level you are at. You probably mean to do the calculation using pixels.
The methods you are looking for are here. fromLatLngToContainerPixel and fromContainerPixelToLatLng or fromLatLngToDivPixel and fromDivPixelToLatLng.
This means you should probably wrap up that code into an OverlayView and call getProjection() on your map to get the projection and then use one set of those methods to do the calculation.