This is the Feature
var singaporeJSON = {"type":"FeatureCollection","geocoding":{"creation_date":"2016-01-09","generator":{"author":{"name":"Mapzen"},"package":"fences-builder","version":"0.1.2"},"license":"ODbL (see http://www.openstreetmap.org/copyright)"},"features":[{"properties":{"name:display":"Singapore","name":"singapore"},"geometry":{"type":"MultiPolygon","coordinates":[[[[104.5706735,1.4419380999999996],[104.55258289999996,1.4106225999999995],[104.3891305,1.3172011999999995],[104.3488465,1.3331733],[104.35531379999998,1.3563739000000001],[104.37827119999996,1.4102812999999996],[104.4633395,1.4991501],[104.48455620000003,1.5130449],[104.486389,1.5123502],[104.4986883,1.5064215999999995],[104.52192920000003,1.4921171000000004],[104.5327622,1.4838079999999998],[104.5430018,1.4747782999999997],[104.5526002,1.4650701999999998],[104.5706735,1.4419380999999996]]],[[[104.12611109999997,1.28925],[104.12517559999996,1.2758195999999995],[104.11490400000002,1.2765354],[104.0927257,1.2733869],[104.0333333,1.2694999999999996],[103.9184908,1.2226474],[103.88074999999998,1.20725],[103.85983329999998,1.1959722],[103.80499999999999,1.1714444],[103.74069440000004,1.1303611],[103.67072219999996,1.1794444],[103.66069440000003,1.1881667],[103.57233329999997,1.1987500000000004],[103.56666670000003,1.1955],[103.60286109999997,1.2641666999999996],[103.6170833,1.3154166999999997],[103.6178333,1.3216111],[103.6300556,1.3410556],[103.64919440000003,1.3799166999999997],[103.65297219999995,1.3870833],[103.6535,1.3912778000000001],[103.6571667,1.4004166999999996],[103.6639167,1.4104721999999998],[103.6694444,1.4157499999999996],[103.67388889999995,1.4281389],[103.68333329999997,1.43725],[103.69405560000003,1.4398889],[103.69886109999997,1.4433055999999995],[103.70375,1.4507778],[103.71411110000003,1.4574721999999998],[103.72833329999997,1.4601389],[103.74677779999998,1.4503889],[103.76069440000003,1.4483889],[103.7711111,1.4527778],[103.7904722,1.4651389],[103.80366670000002,1.4765],[103.81266670000002,1.4784722],[103.8221667,1.4766943999999993],[103.83416670000003,1.4729999999999996],[103.85199999999999,1.4661666999999996],[103.8586667,1.4629443999999996],[103.8649722,1.4588610999999996],[103.8681667,1.4565278],[103.88613889999996,1.4351388999999997],[103.8978611,1.4279443999999992],[103.91275,1.4275277999999996],[103.91683330000002,1.4266666999999993],[103.93341670000002,1.4304166999999997],[103.93769440000005,1.4304721999999999],[103.94252780000002,1.4278332999999992],[103.96066670000003,1.42475],[103.96680560000003,1.4221666999999996],[103.9835833,1.4242778],[103.98616670000003,1.4249166999999996],[103.99525000000003,1.4236943999999991],[104.00286109999998,1.4206111],[104.0233056,1.4391388999999997],[104.0408333,1.4438889],[104.05747219999996,1.4398610999999992],[104.07119440000002,1.4346389],[104.07647219999996,1.4309166999999996],[104.08847219999996,1.4175832999999995],[104.09075,1.4124443999999996],[104.09266670000002,1.4059443999999992],[104.0930278,1.3998055999999999],[104.09247219999996,1.3942500000000004],[104.0835833,1.3687500000000001],[104.07972219999996,1.3575],[104.08527779999997,1.3466666999999997],[104.12611109999997,1.28925]]]]},"type":"Feature"}]}
This is the Code
var w = $('#map').parent().width();
var h = $(document).height() - 100;
var projection = d3.geoEquirectangular()
.scale(w)//scale it down to h / (2*Math.PI)
.translate([-w , h]);//translate as per your choice
//Create SVG element
var svg = d3.select("#map")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("path")
.data(singaporeJSON.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection))
.style("fill", "steelblue");
And the result is, (ref attachment, highlighted by red circle)
How do I scale this without any trial and error, so that it fits the SVG height and width
The answer is in the link
Center a map in d3 given a geoJSON object
referred by Gerado Futado.
This would be the final code
// Create a unit projection.
var projection = d3.geo.albers()
.scale(1)
.translate([0, 0]);
// Create a path generator.
var path = d3.geo.path()
.projection(projection);
// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(feature), // if u have features, use features[0]
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// Update the projection to use computed scale & translate.
projection
.scale(s)
.translate(t);
svg.selectAll("path")
.data(singaporeJSON.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection)) .style("fill", "steelblue");
I'm creating a star field in a three.js scene. The code to generate the random positions of the stars is below. When the stars are rendered and the camera is pulled back enough from the center of the scene, there are a couple of visible "empty" tracks in the placement of the stars.
I'm assuming it has to do with the math in the _addStars method. Can anyone help me to tighten up the placement of the stars throughout the entire canvas?
Note: The canvas I have to work with is somewhere around an 8:1 ratio height:width. So just repositioning the camera is not an option.
UPDATE: I've added a fiddle to demonstrate the issue: https://jsfiddle.net/scottwatkins/5zjoLLpx/5/
/** Method to generate the stars and place them in the particle system */
_addStars: function () {
var starColors = [];
var starGeometry = new THREE.Geometry();
starGeometry.colors = starColors;
for (var i = 0; i < this.totalStars; i++) {
var x = 120 - Math.random() * 1040;
var y = 480 - Math.random() * 1040;
var z = 0 - Math.random() * 1040;
starGeometry.vertices.push( new THREE.Vector3( x, y, z ) );
var starColor = new THREE.Color(0xffffff);
starColor.setRGB(
.8 + Math.random() * .2,
.8 + Math.random() * .2,
.8 + Math.random() * .2);
starColors.push(starColor)
}
var starMaterial = new THREE.PointsMaterial( {
size: 2.0,
map: this.starTexture,
depthTest: false,
depthWrite: false,
blending: THREE.AdditiveBlending,
transparent : true,
vertexColors: true
} );
this.particleSystem = new THREE.Points( starGeometry, starMaterial );
this.scene.add(this.particleSystem);
}
It appears to be caused by Math.random() seems to work with THREE.Math.random16()
var x = 120 - THREE.Math.random16() * 1040;
var y = 480 - THREE.Math.random16() * 1040;
var z = 0 - THREE.Math.random16() * 1040;
Here's what it says in the docs for THREE.Math.random16():
Random float from 0 to 1 with 16 bits of randomness.
Standard Math.random() creates repetitive patterns when applied over larger space.
Updated fiddle: here
I'm trying to find the visible size of a sphere in pixels, after projection to screen space. The sphere is centered at the origin with the camera looking right at it. Thus the projected sphere should be a perfect circle in two dimensions. I am aware of this 1 existing question. However, the formula given there doesn't seem to produce the result I want. It is too small by a few percent. I assume this is because it is not correctly taking perspective into account. After projecting to screen space you do not see half the sphere but significantly less, due to perspective foreshortening (you see just a cap of the sphere instead of the full hemisphere 2).
How can I derive an exact 2D bounding circle?
Indeed, with a perspective projection you need to compute the height of the sphere "horizon" from the eye / center of the camera (this "horizon" is determined by rays from the eye tangent to the sphere).
Notations:
d: distance between the eye and the center of the sphere
r: radius of the sphere
l: distance between the eye and a point on the sphere "horizon", l = sqrt(d^2 - r^2)
h: height / radius of the sphere "horizon"
theta: (half-)angle of the "horizon" cone from the eye
phi: complementary angle of theta
h / l = cos(phi)
but:
r / d = cos(phi)
so, in the end:
h = l * r / d = sqrt(d^2 - r^2) * r / d
Then once you have h, simply apply the standard formula (the one from the question you linked) to get the projected radius pr in the normalized viewport:
pr = cot(fovy / 2) * h / z
with z the distance from the eye to the plane of the sphere "horizon":
z = l * cos(theta) = sqrt(d^2 - r^2) * h / r
so:
pr = cot(fovy / 2) * r / sqrt(d^2 - r^2)
And finally, multiply pr by height / 2 to get the actual screen radius in pixels.
What follows is a small demo done with three.js. The sphere distance, radius and the vertical field of view of the camera can be changed by using respectively the n / f, m / p and s / w pairs of keys. A yellow line segment rendered in screen-space shows the result of the computation of the radius of the sphere in screen-space. This computation is done in the function computeProjectedRadius().
projected-sphere.js:
"use strict";
function computeProjectedRadius(fovy, d, r) {
var fov;
fov = fovy / 2 * Math.PI / 180.0;
//return 1.0 / Math.tan(fov) * r / d; // Wrong
return 1.0 / Math.tan(fov) * r / Math.sqrt(d * d - r * r); // Right
}
function Demo() {
this.width = 0;
this.height = 0;
this.scene = null;
this.mesh = null;
this.camera = null;
this.screenLine = null;
this.screenScene = null;
this.screenCamera = null;
this.renderer = null;
this.fovy = 60.0;
this.d = 10.0;
this.r = 1.0;
this.pr = computeProjectedRadius(this.fovy, this.d, this.r);
}
Demo.prototype.init = function() {
var aspect;
var light;
var container;
this.width = window.innerWidth;
this.height = window.innerHeight;
// World scene
aspect = this.width / this.height;
this.camera = new THREE.PerspectiveCamera(this.fovy, aspect, 0.1, 100.0);
this.scene = new THREE.Scene();
this.scene.add(THREE.AmbientLight(0x1F1F1F));
light = new THREE.DirectionalLight(0xFFFFFF);
light.position.set(1.0, 1.0, 1.0).normalize();
this.scene.add(light);
// Screen scene
this.screenCamera = new THREE.OrthographicCamera(-aspect, aspect,
-1.0, 1.0,
0.1, 100.0);
this.screenScene = new THREE.Scene();
this.updateScenes();
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.setSize(this.width, this.height);
this.renderer.domElement.style.position = "relative";
this.renderer.autoClear = false;
container = document.createElement('div');
container.appendChild(this.renderer.domElement);
document.body.appendChild(container);
}
Demo.prototype.render = function() {
this.renderer.clear();
this.renderer.setViewport(0, 0, this.width, this.height);
this.renderer.render(this.scene, this.camera);
this.renderer.render(this.screenScene, this.screenCamera);
}
Demo.prototype.updateScenes = function() {
var geometry;
this.camera.fov = this.fovy;
this.camera.updateProjectionMatrix();
if (this.mesh) {
this.scene.remove(this.mesh);
}
this.mesh = new THREE.Mesh(
new THREE.SphereGeometry(this.r, 16, 16),
new THREE.MeshLambertMaterial({
color: 0xFF0000
})
);
this.mesh.position.z = -this.d;
this.scene.add(this.mesh);
this.pr = computeProjectedRadius(this.fovy, this.d, this.r);
if (this.screenLine) {
this.screenScene.remove(this.screenLine);
}
geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0.0, 0.0, -1.0));
geometry.vertices.push(new THREE.Vector3(0.0, -this.pr, -1.0));
this.screenLine = new THREE.Line(
geometry,
new THREE.LineBasicMaterial({
color: 0xFFFF00
})
);
this.screenScene = new THREE.Scene();
this.screenScene.add(this.screenLine);
}
Demo.prototype.onKeyDown = function(event) {
console.log(event.keyCode)
switch (event.keyCode) {
case 78: // 'n'
this.d /= 1.1;
this.updateScenes();
break;
case 70: // 'f'
this.d *= 1.1;
this.updateScenes();
break;
case 77: // 'm'
this.r /= 1.1;
this.updateScenes();
break;
case 80: // 'p'
this.r *= 1.1;
this.updateScenes();
break;
case 83: // 's'
this.fovy /= 1.1;
this.updateScenes();
break;
case 87: // 'w'
this.fovy *= 1.1;
this.updateScenes();
break;
}
}
Demo.prototype.onResize = function(event) {
var aspect;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.renderer.setSize(this.width, this.height);
aspect = this.width / this.height;
this.camera.aspect = aspect;
this.camera.updateProjectionMatrix();
this.screenCamera.left = -aspect;
this.screenCamera.right = aspect;
this.screenCamera.updateProjectionMatrix();
}
function onLoad() {
var demo;
demo = new Demo();
demo.init();
function animationLoop() {
demo.render();
window.requestAnimationFrame(animationLoop);
}
function onResizeHandler(event) {
demo.onResize(event);
}
function onKeyDownHandler(event) {
demo.onKeyDown(event);
}
window.addEventListener('resize', onResizeHandler, false);
window.addEventListener('keydown', onKeyDownHandler, false);
window.requestAnimationFrame(animationLoop);
}
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Projected sphere</title>
<style>
body {
background-color: #000000;
}
</style>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r61/three.min.js"></script>
<script src="projected-sphere.js"></script>
</head>
<body onLoad="onLoad()">
<div id="container"></div>
</body>
</html>
Let the sphere have radius r and be seen at a distance d from the observer. The projection plane is at distance f from the observer.
The sphere is seen under the half angle asin(r/d), so the apparent radius is f.tan(asin(r/d)), which can be written as f . r / sqrt(d^2 - r^2). [The wrong formula being f . r / d.]
The illustrated accepted answer above is excellent, but I needed a solution without knowing the field of view, just a matrix to transform between world and screen space, so I had to adapt the solution.
Reusing some variable names from the other answer, calculate the start point of the spherical cap (the point where line h meets line d):
capOffset = cos(asin(l / d)) * r
capCenter = sphereCenter + ( sphereNormal * capOffset )
where capCenter and sphereCenter are points in world space, and sphereNormal is a normalized vector pointing along d, from the sphere center towards the camera.
Transform the point to screen space:
capCenter2 = matrix.transform(capCenter)
Add 1 (or any amount) to the x pixel coordinate:
capCenter2.x += 1
Transform it back to world space:
capCenter2 = matrix.inverse().transform(capCenter2)
Measure the distance between the original and new points in world space, and divide into the amount you added to get a scale factor:
scaleFactor = 1 / capCenter.distance(capCenter2)
Multiply that scale factor by the cap radius h to get the visible screen radius in pixels:
screenRadius = h * scaleFactor
this is (I think) a relatively simple math question but I've spent a day banging my head against it and have only the dents and no solution...
I'm coding in actionscript 3 - the functionality is:
large image loaded at runtime. The bitmapData is stored and a smaller version is created to display on the available screen area (I may end up just scaling the large image since it is in memory anyway).
The user can create a rectangle hotspot on the smaller image (the functionality will be more complex: multiple rects with transparency: example a donut shape with hole, etc)
3 When the user clicks on the hotspot, the rect of the hotspot is mapped to the larger image and a new bitmap "callout" is created, using the larger bitmap data. The reason for this is so the "callout" will be better quality than just scaling up the area of the hotspot.
The image below shows where I am at so far- the blue rect is the clicked hotspot. In the upper left is the "callout" - copied from the larger image. I have the aspect ratio right but I am not mapping to the larger image correctly.
Ugly code below... Sorry this post is so long - I just figured I ought to provide as much info as possible. Thanks for any tips!
--trace of my data values
*source BitmapDada 1152 864
scaled to rect 800 600
scaled BitmapData 800 600
selection BitmapData 58 56
scaled selection 83 80
ratio 1.44
before (x=544, y=237, w=58, h=56)
(x=544, y=237, w=225.04, h=217.28)
*
Image here: http://i795.photobucket.com/albums/yy237/skinnyTOD/exampleST.jpg
public function onExpandCallout(event:MouseEvent):void{
if (maskBitmapData.getPixel32(event.localX, event.localY) != 0){
var maskClone:BitmapData = maskBitmapData.clone();
//amount to scale callout - this will vary/can be changed by user
var scale:Number =150 //scale percentage
var normalizedScale :Number = scale/=100;
var w:Number = maskBitmapData.width*normalizedScale;
var h:Number = maskBitmapData.height*normalizedScale;
var ratio:Number = (sourceBD.width /targetRect.width);
//creat bmpd of the scaled size to copy source into
var scaledBitmapData:BitmapData = new BitmapData(maskBitmapData.width * ratio, maskBitmapData.height * ratio, true, 0xFFFFFFFF);
trace("source BitmapDada " + sourceBD.width, sourceBD.height);
trace("scaled to rect " + targetRect.width, targetRect.height);
trace("scaled BitmapData", bkgnImageSprite.width, bkgnImageSprite.height);
trace("selection BitmapData", maskBitmapData.width, maskBitmapData.height);
trace("scaled selection", scaledBitmapData.width, scaledBitmapData.height);
trace("ratio", ratio);
var scaledBitmap:Bitmap = new Bitmap(scaledBitmapData);
var scaleW:Number = sourceBD.width / scaledBitmapData.width;
var scaleH:Number = sourceBD.height / scaledBitmapData.height;
var scaleMatrix:Matrix = new Matrix();
scaleMatrix.scale(ratio,ratio);
var sRect:Rectangle = maskSprite.getBounds(bkgnImageSprite);
var sR:Rectangle = sRect.clone();
var ss:Sprite = new Sprite();
ss.graphics.lineStyle(8, 0x0000FF);
//ss.graphics.beginFill(0x000000, 1);
ss.graphics.drawRect(sRect.x, sRect.y, sRect.width, sRect.height);
//ss.graphics.endFill();
this.addChild(ss);
trace("before " + sRect);
w = uint(sRect.width * scaleW);
h = uint(sRect.height * scaleH);
sRect.inflate(maskBitmapData.width * ratio, maskBitmapData.height * ratio);
sRect.offset(maskBitmapData.width * ratio, maskBitmapData.height * ratio);
trace(sRect);
scaledBitmapData.copyPixels(sourceBD, sRect, new Point());
addChild(scaledBitmap);
scaledBitmap.x = offsetPt.x;
scaledBitmap.y = offsetPt.y;
}
}
Thanks!
public function onExpandCallout(event:MouseEvent):void{
// TODO: build this on startup or only on click? Speed vs memory
if (calloutState == true) return;
if (maskBitmapData.getPixel32(event.localX, event.localY) != 0){
calloutState = true;
//create bitmap from source using scaled selection rect
var ratio:Number = (sourceBMD.width /targetRect.width);
var sRect:Rectangle = hotSpotSprite.getBounds(bkgnImageSprite);
var destRect:Rectangle = new Rectangle(sRect.x * ratio, sRect.y * ratio, sRect.width * ratio, sRect.height * ratio);
calloutBitmapData = new BitmapData(destRect.width, destRect.height, true, 0xFFFFFFFF);
calloutBitmap = new Bitmap(calloutBitmapData);
//-- scale alpha mask
var scaledMaskBitmapData:BitmapData = new BitmapData(destRect.width, destRect.height, true, 0x00000000);
var maskScale:Number = scaledMaskBitmapData.width / maskBitmapData.width;
var mMatrix:Matrix = new Matrix(maskScale, 0, 0, maskScale);
scaledMaskBitmapData.draw(maskBitmapData,mMatrix,null,null,null, false);
// copy source with scaled alpha
calloutBitmapData.copyPixels(sourceBMD, destRect, new Point(), scaledMaskBitmapData, new Point());
scaledMaskBitmapData = null;
// apply filter to bitmap
var myDropShadowFilter:DropShadowFilter = new DropShadowFilter();
myDropShadowFilter.distance = 12;
myDropShadowFilter.alpha = .3
myDropShadowFilter.strength = 1;
myDropShadowFilter.blurX = 8;
myDropShadowFilter.blurY = 8;
calloutBitmap.filters = [myDropShadowFilter];
//place on screen
calloutSprite = new Sprite();
calloutSprite.addChild(calloutBitmap)
calloutSprite.x = offsetPt.x;
calloutSprite.y = offsetPt.y;
// ADD TO PARENT DisplayContainer
calloutLayer.addChild(calloutSprite);
// calloutSprite.scaleX = 2;
// calloutSprite.scaleY = 2;
calloutSprite.doubleClickEnabled = true;
calloutSprite.addEventListener(MouseEvent.DOUBLE_CLICK, onCollapseCallout);
calloutSprite.addEventListener(MouseEvent.MOUSE_DOWN, onStartDrag);
calloutSprite.addEventListener(MouseEvent.MOUSE_UP, onStopDrag);
}
}