I am a novice programmer working with OpenJScad written in Javascript to build 3D models.
I am trying to figure out how to structure my code so that I can access an object's instance properties that are dynamically created with user input parameters. I have a parent Gear class with the following variable...
// Gear parent class
Gear = function(numTeeth, circularPitch, pressureAngle, clearance, thickness)
{
var pitchRadius = numTeeth * circularPitch / (2 * Math.PI);
I am making several Gear sub-classes that accept user parameters, ie...
// Spur Gear
function makeSpur(params)
{
var gear = new Gear(
params.spurTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.inputBore
);
if(params.inputBore > 0)
{
var inputBore = CSG.cylinder({start: [0,0,-params.thickness2], end:
[0,0,params.thickness2], radius: params.inputBore, resolution: 16});
gear = gear.subtract(inputBore).rotateX(90);
}
return gear;
...and then dynamically generating location coordinates based on the pitchRadius property of another Gear object...
// function main
var spurGear = makeSpur(params);
spurGear = spurGear.translate([-pinionGear.pitchRadius,0,0]);
Everything renders, except when I try to access the pitchRadius property from another Gear instance. Ive read about prototypes and accessing private / public properties, but I just can't figure out how to structure the code so that I can access instance properties in function main.
Here is the full code...
include("gears.jscad");
// Here we define the user editable parameters:
function getParameterDefinitions() {
return [
{ name: 'circularPitch', caption: 'Circular pitch:', type: 'float', initial: 5 },
{ name: 'pressureAngle', caption: 'Pressure angle:', type: 'float', initial: 20 },
{ name: 'clearance', caption: 'Clearance:', type: 'float', initial: 0 },
{ name: 'thickness', caption: 'Thickness of transmission gears:', type: 'float', initial: 5 },
{ name: 'spurTeeth', caption: 'Number of spur teeth:', type: 'int', initial: 32 },
{ name: 'pinionTeeth', caption: 'Number of pinion teeth:', type: 'int', initial: 14 },
{ name: 'bore', caption: 'Radius of shaft:', type: 'float', initial: 5 }
];
}
// Main function
function main(params)
{
// declare parts
spurGear = new makeSpur(params);
pinionGear = new makePinion(params);
// assemble parts
spurGear = spurGear.translate([-pinionGear.pitchRadius, 0, -20]); // BREAKS CODE
pinionGear = pinionGear.translate([-spurGear.pitchRadius, 0, 20]); // BREAKS CODE
return [spurGear,pinionGear];
}
// Spur Gear
function makeSpur(params)
{
var gear = new involuteGear(
params.spurTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.thickness,
params.bore
);
if(params.bore > 0)
{
var bore = CSG.cylinder({start: [0,0,-params.thickness], end: [0,0,params.thickness], radius: params.bore, resolution: 16});
gear = gear.subtract(bore).rotateX(90);
}
return gear;
}
// Pinion Gear
function makePinion(params)
{
var gear = new involuteGear(
params.pinionTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.thickness,
params.bore
);
if(params.bore > 0)
{
var bore = CSG.cylinder({start: [0,0,-params.thickness], end: [0,0,params.thickness], radius: params.bore, resolution: 16});
gear = gear.subtract(bore).rotateX(90);
}
return gear;
}
// title: Gear
// author: Joost Nieuwenhuijse
// license: MIT License
/*
For gear terminology see:
http://www.astronomiainumbria.org/advanced_internet_files/meccanica/easyweb.easynet.co.uk/_chrish/geardata.htm
Algorithm based on:
http://www.cartertools.com/involute.html
circularPitch: The distance between adjacent teeth measured at the pitch circle
*/
function involuteGear(numTeeth, circularPitch, pressureAngle, clearance, thickness)
{
// default values:
if(arguments.length < 3) pressureAngle = 20;
if(arguments.length < 4) clearance = 0;
if(arguments.length < 4) thickness = 1;
var addendum = circularPitch / Math.PI;
var dedendum = addendum + clearance;
// radiuses of the 4 circles:
this.pitchRadius = numTeeth * circularPitch / (2 * Math.PI);
// this.getpitchRadius = function() {
//return pitchRadius;
//};
var baseRadius = this.pitchRadius * Math.cos(Math.PI * pressureAngle / 180);
var outerRadius = this.pitchRadius + addendum;
var rootRadius = this.pitchRadius - dedendum;
var maxtanlength = Math.sqrt(outerRadius*outerRadius - baseRadius*baseRadius);
var maxangle = maxtanlength / baseRadius;
var tl_at_pitchcircle = Math.sqrt(this.pitchRadius*this.pitchRadius - baseRadius*baseRadius);
var angle_at_pitchcircle = tl_at_pitchcircle / baseRadius;
var diffangle = angle_at_pitchcircle - Math.atan(angle_at_pitchcircle);
var angularToothWidthAtBase = Math.PI / numTeeth + 2*diffangle;
// build a single 2d tooth in the 'points' array:
var resolution = 5;
var points = [new CSG.Vector2D(0,0)];
for(var i = 0; i <= resolution; i++)
{
// first side of the tooth:
var angle = maxangle * i / resolution;
var tanlength = angle * baseRadius;
var radvector = CSG.Vector2D.fromAngle(angle);
var tanvector = radvector.normal();
var p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[i+1] = p;
// opposite side of the tooth:
radvector = CSG.Vector2D.fromAngle(angularToothWidthAtBase - angle);
tanvector = radvector.normal().negated();
p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[2 * resolution + 2 - i] = p;
}
// create the polygon and extrude into 3D:
var tooth3d = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var allteeth = new CSG();
for(var j = 0; j < numTeeth; j++)
{
var ang = j*360/numTeeth;
var rotatedtooth = tooth3d.rotateZ(ang);
allteeth = allteeth.unionForNonIntersecting(rotatedtooth);
}
// build the root circle:
points = [];
var toothAngle = 2 * Math.PI / numTeeth;
var toothCenterAngle = 0.5 * angularToothWidthAtBase;
for(var k = 0; k < numTeeth; k++)
{
var angl = toothCenterAngle + k * toothAngle;
var p1 = CSG.Vector2D.fromAngle(angl).times(rootRadius);
points.push(p1);
}
// create the polygon and extrude into 3D:
var rootcircle = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var result = rootcircle.union(allteeth);
// center at origin:
result = result.translate([0, 0, -thickness/2]);
return result;
}
I noticed that you are actually returning CSG object in the constructor, so try to use properties container described in OpenJSCAD User guide. According to the guide properties variable is intended to store metadata for the object.
This is an example from guide:
var cube = CSG.cube({radius: 1.0});
cube.properties.aCorner = new CSG.Vector3D([1, 1, 1]);
Additional comments:
You are returning different object then this in your constructor
If you will do something like this: gear = gear.rotateX(90); then you have new object
If you will use properties then metadata is cloned when you do transformation.
Related
The following code is conceived to create a thumbnail image of landsat 7 images:
// Feature Collection
var aoi = geometry
print(aoi);
Map.addLayer(aoi);
var centroid = aoi.centroid(1)
print(centroid);
var coors = centroid.coordinates().getInfo()
var x = coors[0]
var y = coors[1];
Map.setCenter(x, y, 10);
// Elaborating the dates
// Getting Temperatures for Every Month
var period = ['-01-01', '-12-01'];
var years = [['1999', '2000'],
['2000', '2001'],
['2001', '2002'],
['2002', '2003'],
['2003', '2004'],
['2004', '2005'],
['2005', '2006'],
['2006', '2007'],
['2007', '2008'],
['2008', '2009'],
['2009', '2010'],
['2010', '2011'],
['2011', '2012'],
['2012', '2013'],
['2013', '2014'],
];
var add_period = function(year){
var start_date = period[0];
var end_date = period[1];
return [year[0] + start_date, year[1] + end_date];
};
var concatenate_year_with_periods = function(years, period){
return years.map(add_period);
};
var Dates = concatenate_year_with_periods(years, period);
/**********************************************************************
Landsat 7
***********************************************************************/
var visualization = {
bands: ['B4', 'B3', 'B2'],
min: 0.0,
max: 0.3,
};
var visualization_ = {
bands: ['B4_median', 'B3_median', 'B2_median'],
min: 0.0,
max: 0.3,
gamma: [0.95, 1.1, 1]
};
// Applies scaling factors.
var cloudMaskL7 = function(image) {
var qa = image.select('BQA');
var cloud = qa.bitwiseAnd(1 << 4)
.and(qa.bitwiseAnd(1 << 6))
.or(qa.bitwiseAnd(1 << 8));
var mask2 = image.mask().reduce(ee.Reducer.min());
return image
//.select(['B3', 'B4'], ['Red', 'NIR'])
.updateMask(cloud.not()).updateMask(mask2)
.set('system:time_start', image.get('system:time_start'));
};
var dataset = ee.ImageCollection('LANDSAT/LE07/C01/T1_TOA')
.filterDate('1999-01-01', '2020-12-31')
.filterBounds(aoi)
//.map(applyScaleFactors)
.map(cloudMaskL7)
.map(function(image){return image.clip(aoi)});
// Creating composites using median pixel value
var median_yearly_landsat_7 = function(start, end){
var dataset_ = dataset.filter(ee.Filter.date(start, end));
var median_yearly = dataset_.reduce(ee.Reducer.median());
return median_yearly;
};
var composite_name_list_l7 = ee.List([]);
var apply_monthly_composite = function(date_list){
var start = date_list[0];
var end = date_list[1];
var output_name = start + "TO" + end + "_LANSAT_7";
var composite = median_yearly_landsat_7(start, end);
composite_name_list_l7 = composite_name_list_l7.add([composite, output_name]);
Map.addLayer(composite, visualization_, output_name, false);
Export.image.toDrive({
image: composite,
description: output_name,
fileFormat: 'GeoTIFF',
crs : 'EPSG:4326',
folder : 'LANDSAT_LST_LAS_LOMAS',
region: aoi
});
return 0;
};
Dates.map(apply_monthly_composite);
/******************************************************************
// Animation gif
// Create RGB visualization images for use as animation frames.
/******************************************************************/
var text = require('users/gena/packages:text');
var annotated_collection_list = ee.List([])
var annotations = [
{position: 'left', offset: '0.25%', margin: '0.25%', property: 'label', scale: 1.5} //large scale because image if of the whole world. Use smaller scale otherwise
];
var create_annotated_collection = function(image_and_id) {
var img = image_and_id[0];
var image_id = image_and_id[1];
console.log(img);
console.log(image_id);
var img_out = img.visualize(visualization_)
.clip(aoi)//.paint(municipalities, 'FF0000', 2)
.set({'label': image_id});
Map.addLayer(img_out);
var annotated = text.annotateImage(img_out, {}, Bayern, annotations);
annotated_collection.add(annotated);
return 0;
};
var municipalities_geom = geometry;
var n = composite_name_list_l7.size().getInfo();
print(n);
for (var i = 0; i < n; i++) {
var img_info = ee.List(composite_name_list_l7.get(i));
print(img_info);
var img = ee.Image(img_info.get(0));
var img_id = ee.String(img_info.get(1));
var year = ee.String(ee.List(img_id.split("-").get(0)));
var month = ee.String(ee.List(img_id.split("-").get(1)));
var img_id_ = year.getInfo() + "_" + month.getInfo();
var img_out = img.visualize(visualization_)
.set({'label': img_id_});
var annotated = text.annotateImage(img_out, {}, municipalities_geom, annotations);
Map.addLayer(annotated);
var annotated_collection_list = annotated_collection_list.add(annotated)
}
var annotated_col = ee.ImageCollection(annotated_collection_list)
// Define GIF visualization parameters.
var gifParams = {
'region': geometry,
'dimensions': 254,
'crs': 'EPSG:32632',
'framesPerSecond': 1
};
// Print the GIF URL to the console.
print(annotated_col.getVideoThumbURL(gifParams));
// Render the GIF animation in the console.
print(ui.Thumbnail(annotated_col, gifParams));
However, this thumbnail appears black, but the images I load into the map project, are in the color visualization parameters that I need.
The geometry parameter is a Polygon I drew using the drawing tool. The coordinates are below:
Coordinates: List (1 element)
0: List (5 elements)
0: [-82.35277628512759,8.432445555054713]
1: [-82.314667459444,8.432445555054713]
2: [-82.314667459444,8.460632259476993]
3: [-82.35277628512759,8.460632259476993]
4: [-82.35277628512759,8.432445555054713]
Could someone tell my why the thumbnail appears black?
I'm using threejs to render some models (gltf/glb). All of them are of different sizes, some are big and some are small. Now I want scale all of them to the same size.
if i use mesh.scale() that would scale the object relative to it's own size. Is there any way to achieve this without manually calculating each model's scale?
UPDATE:
Here's my code
function loadModels(points) {
// loader
const loader = new GLTFLoader();
const dl = new DRACOLoader();
dl.setDecoderPath("/scripts/decoder/");
loader.setDRACOLoader(dl);
let lengthRatios;
const meshes = [];
for (let i = 0; i < store.length; i++) {
loader.load(
store[i].model,
(gltf) => {
const mesh = gltf.scene;
const meshBounds = new THREE.Box3().setFromObject(mesh);
// Calculate side lengths of model1
const lengthMeshBounds = {
x: Math.abs(meshBounds.max.x - meshBounds.min.x),
y: Math.abs(meshBounds.max.y - meshBounds.min.y),
z: Math.abs(meshBounds.max.z - meshBounds.min.z),
};
if (lengthRatios) {
lengthRatios = [
lengthRatios[0] / lengthMeshBounds.x,
lengthRatios[1] / lengthMeshBounds.y,
lengthRatios[2] / lengthMeshBounds.z,
];
} else {
lengthRatios = [
lengthMeshBounds.x,
lengthMeshBounds.y,
lengthMeshBounds.z,
];
}
meshes.push(mesh);
if (meshes.length == store.length) {
addModels();
}
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
(error) => {
console.log("An error happened");
}
);
}
function addModels() {
// Select smallest ratio in order to contain the models within the scene
const minRation = Math.min(...lengthRatios);
for (let i = 0; i < meshes.length; i++) {
// Use smallest ratio to scale the model
meshes[i].scale.set(minRation, minRation, minRation);
// position the model/mesh
meshes[i].position.set(...points[i]);
// add it to the scene
scene.add(meshes[i]);
}
}
}
You should create a bounding box around each model.
//Creating the actual bounding boxes
mesh1Bounds = new THREE.Box3().setFromObject( model1 );
mesh2Bounds = new THREE.Box3().setFromObject( model2 );
// Calculate side lengths of model1
let lengthMesh1Bounds = {
x: Math.abs(mesh1Bounds.max.x - mesh1Bounds.min.x),
y: Math.abs(mesh1Bounds.max.y - mesh1Bounds.min.y),
z: Math.abs(mesh1Bounds.max.z - mesh1Bounds.min.z),
};
// Calculate side lengths of model2
let lengthMesh2Bounds = {
x: Math.abs(mesh2Bounds.max.x - mesh2Bounds.min.x),
y: Math.abs(mesh2Bounds.max.y - mesh2Bounds.min.y),
z: Math.abs(mesh2Bounds.max.z - mesh2Bounds.min.z),
};
// Calculate length ratios
let lengthRatios = [
(lengthMesh1Bounds.x / lengthMesh2Bounds.x),
(lengthMesh1Bounds.y / lengthMesh2Bounds.y),
(lengthMesh1Bounds.z / lengthMesh2Bounds.z),
];
// Select smallest ratio in order to contain the models within the scene
let minRatio = Math.min(...lengthRatios);
// Use smallest ratio to scale the model
model.scale.set(minRatio, minRatio, minRatio);
So I am trying to create a satellite tracker using Cesium.js and Satellite.js. After countless attempts, I am still unable to to use Polyline to create a line to show the orbital path of the satellite. I have managed to display a point that resembles the satellite orbiting the globe, I just need the actual line to aid the visualisation.
I have looked through the Cesium Documentation multiple times but most of the examples don't seem to work.
If anyone could help I would really appreciate it!
const viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: new Cesium.TileMapServiceImageryProvider({
url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
}),
geocoder: false, infoBox: false,
navigationHelpButton: false
});
viewer.scene.globe.enableLighting = true;
const polylines = new Cesium.PolylineCollection({show: true});
let currentTLE;
function plotSAT() {
for (let i = 0; i < listOfTLE.length; i++) {
try {
currentTLE = listOfTLE[i];
const TLErec = satellite.twoline2satrec(
currentTLE.split('\n')[0].trim(),
currentTLE.split('\n')[1].trim()
);
const totalSeconds = 60 * 60 * 6;
const timestepInSeconds = 10;
const start = Cesium.JulianDate.fromDate(new Date());
const stop = Cesium.JulianDate.addSeconds(start, totalSeconds, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.timeline.zoomTo(start, stop);
viewer.clock.multiplier = 40;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
const positionsOverTime = new Cesium.SampledPositionProperty();
let p;
let position;
for (let i = 0; i < totalSeconds; i+= timestepInSeconds) {
const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
const jsDate = Cesium.JulianDate.toDate(time);
const positionAndVelocity = satellite.propagate(TLErec, jsDate);
const gmst = satellite.gstime(jsDate);
p = satellite.eciToGeodetic(positionAndVelocity.position, gmst);
position = Cesium.Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);
positionsOverTime.addSample(time, position);
}
//Set Random Color
let randomColor = Math.floor(Math.random()*16777215).toString(16);
// Visualize the satellite with a red dot.
const satellitePoint = viewer.entities.add({
position: positionsOverTime,
point: { pixelSize: 5, color: Cesium.Color.fromCssColorString(`#${randomColor}`) },
});
const orbitalLine = polylines.add({
show: true,
id: i,
positions: positionsOverTime,
material: Cesium.Material({
fabric: {
type: 'Color',
uniforms: {
color: Cesium.Color.fromCssColorString(`#${randomColor}`)
}
},
}),
})
viewer.entities.add(polylines);
} catch(err) {
console.log(`Error Loading TLE: ${currentTLE}`);
continue;
}
}
}
plotSAT();
polylines.update();
// Wait for globe to load then zoom out
let initialized = false;
viewer.scene.globe.tileLoadProgressEvent.addEventListener(() => {
if (!initialized && viewer.scene.globe.tilesLoaded === true) {
viewer.clock.shouldAnimate = true;
initialized = true;
viewer.scene.camera.zoomOut(7000000);
document.querySelector("#loading").classList.toggle('disappear', true)
}
});
This is the specific part I need help with:
// Visualize the satellite with a red dot.
const satellitePoint = viewer.entities.add({
position: positionsOverTime,
point: { pixelSize: 5, color: Cesium.Color.fromCssColorString(`#${randomColor}`) },
});
const orbitalLine = polylines.add({
show: true,
id: i,
positions: positionsOverTime,
material: Cesium.Material({
fabric: {
type: 'Color',
uniforms: {
color: Cesium.Color.fromCssColorString(`#${randomColor}`)
}
},
}),
})
viewer.entities.add(polylines);
I managed to get this working for a single TLE by adding the positions to a polyline in the viewer entities, instead of using a Polylines Collection:
const totalSeconds = 60 * 60 * 6;
const timestepInSeconds = 10;
const start = JulianDate.fromDate(new Date());
const positions = [];
for (let i = 0; i < totalSeconds; i += timestepInSeconds) {
const time = JulianDate.addSeconds(start, i, new JulianDate());
const jsDate = JulianDate.toDate(time);
const positionAndVelocity = propagate(TLErec, jsDate);
const gmst = gstime(jsDate);
Log.debug("position and velocity: ", positionAndVelocity)
// #ts-ignore
const p = eciToGeodetic(positionAndVelocity.position, gmst);
const position = Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);
positions.push(position);
}
viewer.entities.add({
name: "orbit line",
polyline: {
positions: positions,
material: Color.RED,
width: 1,
}
});
Note: it may also be worth referring to the answer here about adding the polyline collection to the primitives instead of entities. How to display a polyline collection in Cesium?
I'm new to Javascript and therefore requireJS - I'm currently teaching myself using Openlayers 3 examples, of which, I've just been appending to one large JS file. Seeing that this is becoming unruly very quickly, I read up about RequireJS and thought I should get into the habit of doing things right from the onset; 'which is where I've hit issues'.
[Not that I imagine it matters, but i'm using Asp.net MVC]
Basically, I wish to break the file up into smaller related modules e.g.
Map [which is used by all modules and initiates the base layer map]
Draw [handles points / polygons etc. and is added to the map as
another layer]
Geolocation [contains geolocation functions for plotting]
etc., etc.
...giving the flexibility to have all layers activated at once, or a select few with easy to manage JS code.
I have had several attempts at breaking this code up into such individual JS files, [map / draw / Geolocation] and all fail as I feel I'm not grasping the requireJS methodology (so as not to confuse readers and myself further, I'm neglecting to add my attempts).
Here is the basic code that works:
require.config({
baseUrl: "/Scripts",
paths: {
//jquery: "/lib/jquery-1.11.1.min",
ol: [
"http://openlayers.org/en/v3.8.1/build/ol",
"/lib/ol"
],
domReady: "/lib/domReady"
},
//map: { main: { test: "/Modules/Test/scripts/test" } },
//The shim section is to tell RequireJS about any dependencies your files have before they can be used.
//Here, we are saying if we call “ol” to load that module, we have to load “jquery” first.
//shim: {
//ol: ["jquery"]
//},
//packages: [
// {
//name: 'test',
//location: 'http://...
//main: 'main'
//}]
});
File I wish to break-up:
define(["ol"], function (ol) {
$(document).ready(function () {
//****************
//------MAP-------
//Setup Map Base
// creating the view
var view = new ol.View({
center: ol.proj.fromLonLat([5.8713, 45.6452]),
zoom: 19
});
// creating the map
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: "map",
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: view
});
//****************
//-----DRAW------
var features = new ol.Collection();
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector({ features: features }),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(255, 255, 255, 0.2)"
}),
stroke: new ol.style.Stroke({
color: "#ffcc33",
width: 2
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: "#ffcc33"
})
})
})
});
featureOverlay.setMap(map);
var modify = new ol.interaction.Modify({
features: features,
// the SHIFT key must be pressed to delete vertices, so
// that new vertices can be drawn at the same position
// of existing vertices
deleteCondition: function (event) {
return ol.events.condition.shiftKeyOnly(event) &&
ol.events.condition.singleClick(event);
}
});
map.addInteraction(modify);
var draw; // global so we can remove it later
function addInteraction() {
draw = new ol.interaction.Draw({
features: features,
type: /** #type {ol.geom.GeometryType} */ (typeSelect.value)
});
map.addInteraction(draw);
}
var typeSelect = document.getElementById("type");
/**
* Let user change the geometry type.
* #param {Event} e Change event.
*/
typeSelect.onchange = function (e) {
map.removeInteraction(draw);
addInteraction();
};
addInteraction();
//****************
//---GEOLOCATION---//
// Common app code run on every page can go here
// Geolocation marker
var markerEl = document.getElementById("geolocation_marker");
var marker = new ol.Overlay({
positioning: "center-center",
element: markerEl,
stopEvent: false
});
map.addOverlay(marker);
// LineString to store the different geolocation positions. This LineString
// is time aware.
// The Z dimension is actually used to store the rotation (heading).
var positions = new ol.geom.LineString([],
/** #type {ol.geom.GeometryLayout} */ ("XYZM"));
// Geolocation Control
var geolocation = new ol.Geolocation( /** #type {olx.GeolocationOptions} */({
projection: view.getProjection(),
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000
}
}));
var deltaMean = 500; // the geolocation sampling period mean in ms
// Listen to position changes
geolocation.on("change", function (evt) {
var position = geolocation.getPosition();
var accuracy = geolocation.getAccuracy();
var heading = geolocation.getHeading() || 0;
var speed = geolocation.getSpeed() || 0;
var m = Date.now();
addPosition(position, heading, m, speed);
var coords = positions.getCoordinates();
var len = coords.length;
if (len >= 2) {
deltaMean = (coords[len - 1][3] - coords[0][3]) / (len - 1);
}
var html = [
"Position: " + position[0].toFixed(2) + ", " + position[1].toFixed(2),
"Accuracy: " + accuracy,
"Heading: " + Math.round(radToDeg(heading)) + "°",
"Speed: " + (speed * 3.6).toFixed(1) + " km/h",
"Delta: " + Math.round(deltaMean) + "ms"
].join("<br />");
document.getElementById("info").innerHTML = html;
});
geolocation.on("error", function () {
alert("geolocation error");
// FIXME we should remove the coordinates in positions
});
// convert radians to degrees
function radToDeg(rad) {
return rad * 360 / (Math.PI * 2);
}
// convert degrees to radians
function degToRad(deg) {
return deg * Math.PI * 2 / 360;
}
// modulo for negative values
function mod(n) {
return ((n % (2 * Math.PI)) + (2 * Math.PI)) % (2 * Math.PI);
}
function addPosition(position, heading, m, speed) {
var x = position[0];
var y = position[1];
var fCoords = positions.getCoordinates();
var previous = fCoords[fCoords.length - 1];
var prevHeading = previous && previous[2];
if (prevHeading) {
var headingDiff = heading - mod(prevHeading);
// force the rotation change to be less than 180°
if (Math.abs(headingDiff) > Math.PI) {
var sign = (headingDiff >= 0) ? 1 : -1;
headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
}
heading = prevHeading + headingDiff;
}
positions.appendCoordinate([x, y, heading, m]);
// only keep the 20 last coordinates
positions.setCoordinates(positions.getCoordinates().slice(-20));
// FIXME use speed instead
if (heading && speed) {
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //"data/geolocation_marker_heading.png";F:\DeleteMeThree\_Orchard-19x\src\Orchard.Web\Modules\Cns.OL\Contents/Images/geolocation_marker.png
} else {
//alert(markerEl.src); PETE: Not sure if this is std OL practice, but this is achieved by already having an element
//called "geolocation_marker" in the dom as an img, which this uses? Strange to me
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //I added img via media module - ridiculous?!
}
}
var previousM = 0;
// change center and rotation before render
map.beforeRender(function (map, frameState) {
if (frameState !== null) {
// use sampling period to get a smooth transition
var m = frameState.time - deltaMean * 1.5;
m = Math.max(m, previousM);
previousM = m;
// interpolate position along positions LineString
var c = positions.getCoordinateAtM(m, true);
var view = frameState.viewState;
if (c) {
view.center = getCenterWithHeading(c, -c[2], view.resolution);
view.rotation = -c[2];
marker.setPosition(c);
}
}
return true; // Force animation to continue
});
// recenters the view by putting the given coordinates at 3/4 from the top or
// the screen
function getCenterWithHeading(position, rotation, resolution) {
var size = map.getSize();
var height = size[1];
return [
position[0] - Math.sin(rotation) * height * resolution * 1 / 4,
position[1] + Math.cos(rotation) * height * resolution * 1 / 4
];
}
// postcompose callback
function render() {
map.render();
}
//EMP
//$("#geolocate").click(function () {
// alert("JQuery Running!");
//});
// geolocate device
var geolocateBtn = document.getElementById("geolocate");
geolocateBtn.addEventListener("click", function () {
geolocation.setTracking(true); // Start position tracking
map.on("postcompose", render);
map.render();
disableButtons();
}, false);
});
})
Considering that i'll have many more modules to attach in the future, what would be the best way to break-up this code using RequireJS for efficiency and coding functionality / maintenance.
Thanks ever so much for your guidance / thoughts, cheers WL
Every require module (defined using define) is supposed to return a function/object. The breakup shown in question doesn't, instead just splits the code. Think of some hypothetical module buckets and put each piece of code (or function) into a module. Then group the code into a require js module and return the module's interface.
Let me try to explain further with an example.
main.js
$(document).ready(function(){
$("#heyINeedMap").click(function(){
require(['map'],function(Map){
Map.render($target);
});
});
});
OR
$(document).ready(function(){
require(['map','geolocation'],function(Map,Geolocation){
window.App.start = true;
window.App.map = Map; //won't suggest, but you can do.
window.App.geolocation = Geolocation;
//do something.
$("#lastCoords").click(function(){
var coords = App.geolocation.getLastSavedCoords();
if(!!coords){
coords = App.geolocation.fetchCurrentCoords();
}
alert(coords);
});
});
});
map.js
define(['jquery'],function($){
var privateVariableAvailableToAllMapInstances = 'something';
var mapType = 'scatter';
return function(){
render: function(el){
//rendering logic goes here
},
doSomethingElse: function(){
privateVariable = 'some new value';
//other logic goes here
},
changeMapType: function(newType){
mapType = newType;
//...
}
}
});
geolocation.js
//Just assuming that it needs jquery & another module called navigation to work.
define(['jquery','navigation'], function($,Gnav){
return {
var coordinates = Gnav.lastSavedCoords;
fetchCurrentCoords: function(){
//coordinates = [79.12213, 172.12342]; //fetch from API/something
return coordinates;
},
getLastSavedCoords: function(){
return coordinates;
}
}
});
Hope this gives an idea on how to proceed.
I am trying to draw a line with arrow at the tip of the line. But i am unable to complete it. Can any one help me.
I tried adding triangle with line but unfortunately it wont work while dynamically drawing. This is what i tried along with line.
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
}
function onMouseDrag(event) {
myPath.add(event.point);
}
function onMouseUp(event) {
var myCircle = new Path.RegularPolygon(event.point, 3, 10);
myCircle.strokeColor = 'black';
myCircle.fillColor = 'white';
}
you draw line with arrow with this code ,
paper.Shape.ArrowLine = function (sx, sy, ex, ey, isDouble) {
function calcArrow(px0, py0, px, py) {
var points = [];
var l = Math.sqrt(Math.pow((px - px0), 2) + Math.pow((py - py0), 2));
points[0] = (px - ((px - px0) * Math.cos(0.5) - (py - py0) * Math.sin(0.5)) * 10 / l);
points[1] = (py - ((py - py0) * Math.cos(0.5) + (px - px0) * Math.sin(0.5)) * 10 / l);
points[2] = (px - ((px - px0) * Math.cos(0.5) + (py - py0) * Math.sin(0.5)) * 10 / l);
points[3] = (py - ((py - py0) * Math.cos(0.5) - (px - px0) * Math.sin(0.5)) * 10 / l);
return points;
}
var endPoints = calcArrow(sx, sy, ex, ey);
var startPoints = calcArrow(ex, ey, sx, sy);
var e0 = endPoints[0],
e1 = endPoints[1],
e2 = endPoints[2],
e3 = endPoints[3],
s0 = startPoints[0],
s1 = startPoints[1],
s2 = startPoints[2],
s3 = startPoints[3];
var line = new paper.Path({
segments: [
new paper.Point(sx, sy),
new paper.Point(ex, ey)
],
strokeWidth: 1
});
var arrow1 = new paper.Path({
segments: [
new paper.Point(e0, e1),
new paper.Point(ex, ey),
new paper.Point(e2, e3)
]
});
var compoundPath = new paper.CompoundPath([line, arrow1]);
if (isDouble === true) {
var arrow2 = new paper.Path({
segments: [
new paper.Point(s0, s1),
new paper.Point(sx, sy),
new paper.Point(s2, s3)
]
});
compoundPath.addChild(arrow2);
}
return compoundPath;};
use
tool.onMouseDrag = function (event) { this.item = new paper.Shape.ArrowLine(event.downPoint.x, event.downPoint.y, event.point.x, event.point.y);
this.item.removeOnDrag();}
There is an example code in paperjs refrence which draws an arrow at the end of a vector.
Have a look at: http://paperjs.org/tutorials/geometry/vector-geometry/ (scroll all the way down to the end of the page)
A simple approach is to create a group that consists of the vector itself (the line) and the arrow head. lineStart and lineEnd are the points where the line of the arrow starts and ends.
// parameters
headLength = 10;
headAngle = 150;
lineStart = new Point(200, 200);
lineEnd = new Point (250, 250);
tailLine = new Path.Line(lineStart, lineEnd);
tailVector = lineEnd - lineStart;
headLine = tailVector.normalize(headLength);
arrow = new Group([
new Path([lineStart, lineEnd]),
new Path([
lineEnd + headLine.rotate(headAngle),
lineEnd,
lineEnd + headLine.rotate(-headAngle)
])
]);
arrow.strokeColor = 'black';
And, as previously mentioned, if you want to recreate it each time then you can make the previous code a function, something like:
// parameters
var headLength = 10;
var headAngle = 150;
var arrowColor = 'black';
// the arrow
var arrow = null;
function drawArrow(start, end) {
var tailLine = new Path.Line(start, end);
var tailVector = end - start;
var headLine = tailVector.normalize(headLength);
arrow = new Group([
new Path([start, end]),
new Path([
end + headLine.rotate(headAngle),
end,
end + headLine.rotate(-headAngle)
])
]);
arrow.strokeColor = arrowColor;
}
tool.onMouseDrag = function(e) {
if (arrow) {
arrow.remove();
}
drawArrow(e.downPoint, e.point);
}
Here is a sketch of the previous code sketch.paperjs.org
Here's an example, where I extend paper.Group.
In my example I avoid redrawing the arrow on each mousedrag. I create once on mousedown and then transform the line and the arrow parts to the appropriate position/rotation on each mousedrag.
Note: I'm fairly certain the following code can be improved a lot.
Drawing via mouse events
'use strict'
/* Arrow Class extends Group */
const Arrow = paper.Group.extend({
initialize: function (args) {
paper.Group.call(this, args)
this._class = 'Arrow'
this._serializeFields = Object.assign(this._serializeFields, {
from: null,
to: null,
headSize: null
})
this.from = args.from
this.to = args.to || args.from
this.headSize = args.headSize
// #NOTE
// `paper.project.importJSON()` passes the deserialized children
// (the arrow parts) to the `Group` superclass so there's no need to
// create them again.
if (this.children.length)
return
this.addChildren([
new paper.Path({
...args,
segments: [
this.from,
this.from
]
}),
new paper.Path({
...args,
segments: [
this.from,
this.from
]
}),
new paper.Path({
...args,
segments: [
this.from,
this.from
]
})
])
this.update(this.to)
},
update: function (point) {
const angle = this.from.subtract(point).angle - 90
this.children[0].lastSegment.point = point
this.children[1].firstSegment.point = point
this.children[1].lastSegment.point = point.add(
this.headSize,
this.headSize
)
this.children[2].firstSegment.point = point
this.children[2].lastSegment.point = point.add(
-this.headSize,
this.headSize
)
this.children[1].rotate(angle, point)
this.children[2].rotate(angle, point)
return this
}
})
paper.Base.exports.Arrow = Arrow
/* Usage */
paper.setup(document.querySelector('canvas'))
const tool = new paper.Tool()
let arrow
tool.onMouseDown = e => {
arrow = new Arrow({
from: e.point,
headSize: 10,
strokeWidth: 1,
strokeColor: '#555',
strokeCap: 'round'
})
}
tool.onMouseDrag = e => {
arrow.update(e.point)
}
canvas {
display: block;
width: 100%;
height: 100%;
margin: 0;
background: #fafafa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<canvas resize="true"></canvas>
... or just draw to a static position
If you want to just draw the arrow (without using mouse events), just do the following:
const arrow = new Arrow({
from: new paper.Point(100, 100),
to: new paper.Point(200, 200),
headSize: 10,
strokeWidth: 1,
strokeColor: '#555',
strokeCap: 'round'
})