WaBis

walter.bislins.ch

JavaScript: Simulation of the Eearths Curvature

JavaScript for the earth curvature simulator on page Finding the curvature of the Earth.

JavaScript für den Erd-Krümmungs-Simulator der Seite Wie stark ist die Krümmung der Erde?.

#INCLUDE JsGraphX3D.inc
#INCLUDE ControlPanel.inc

<jscript>

function EarthModel() {

  this.rEarth = 6371000;
  this.rFEarth = this.rEarth * Math.PI / 2;
  this.nLines = 45;
  this.showModel = 1; // 1 -> globe, 2 -> flat, 3 -> both
  this.showGrid = 1; // 0 -> none, 1 -> globe, 3 -> both
  this.showFlatHorizon = false;
  this.showEquator = false;
  this.showEyeLevel = true;
  this.deviceRatio = 3 / 2; // width / height of device screen
  this.sceneWidth = 0;
  this.sceneHeight = 0;

  this.HeightSlider = 0;
  this.HeightSliderLast = 0;
  this.HeightRange = 0; // 0 -> log, > 0 -> linear
  this.Height = 10000;

  this.ViewAngle = 60;  // viewAngle in deg
  this.ViewAngleField = 60;
  this.ViewAngleSlider = 0;
  this.ViewAngleSliderLast = 0;
  this.Roll = 0;
  this.Nick = 0;
  this.FocalLength = 0;
  this.FocalLengthField = 0;
  this.FocalLengthSlider = 0;
  this.FocalLengthSliderLast = 0;

  this.rDisk = 0;          // d
  this.dHorizon = 0;       // s
  this.zDisk = 0;          // p
  this.hDisk = 0;          // R - b
  this.hDip = 0;           // b
  this.aDip = 0;           // alpha
  this.aEarth = 0;         // 180 - 2 * alpha
  this.dView = 0;          // v
  this.aDelta = 0;         // aDip / nLines
  this.posEarth = [ 0, 0, -this.rEarth ];
  this.camViewAngle = 0.1; // rad
  this.camPos = [ 0, 0, 0 ];
  this.camUp = [ 0, 0, 1 ];
  this.camViewCenter = [ 0, 1, 0 ];
  this.camSceneSize = 1;

  this.drawBackGGrid = false;
  this.drawBackFGrid = false;
  this.drawBackFE = false;

  this.Update();
}

EarthModel.prototype.Update = function() {
  var pi180 = Math.PI;
  var pi90 = pi180 / 2;
  var pi45 = pi180 / 4;
  var toRad  = pi180 / 180;

  if ( this.rEarth < 100000 ) this.rEarth = 100000;
  this.rFEarth = this.rEarth * Math.PI / 2;

  // handle height changes
  if ( this.HeightSliderLast != this.HeightSlider ) {
    if ( this.HeightRange == 0 ) {
      this.Height = Math.pow( 10, 1 + 8 * this.HeightSlider );
    } else {
      this.Height = this.HeightRange * this.HeightSlider;
    }
  }
  if ( this.Height < 0.1 ) this.Height = 0.1;
  if ( this.Height > 1000000000 ) this.Height = 1000000000;
  if ( this.HeightRange == 0 ) {
    this.HeightSlider = ( Math.log10( this.Height ) - 1 ) / 8;
  } else {
    this.HeightSlider = this.Height / this.HeightRange;
  }
  this.HeightSliderLast = this.HeightSlider;

  // handle ViewAngle and FocalLength changes
  if ( this.FocalLengthSlider != this.FocalLengthSliderLast ) {
    this.ViewAngle = 2 * Math.atan( 43.2 / 2 / this.FocalLengthSlider ) / toRad;
  } else if ( this.FocalLengthField != this.FocalLength ) {
    this.ViewAngle = 2 * Math.atan( 43.2 / 2 / this.FocalLengthField ) / toRad;
  } else if ( this.ViewAngleSlider != this.ViewAngleSliderLast ) {
    this.ViewAngle = this.ViewAngleSlider;
  } else if ( this.ViewAngleField != this.ViewAngle ) {
    this.ViewAngle = this.ViewAngleField;
  }
  if ( this.ViewAngle < 0.1 ) this.ViewAngle = 0.1;
  if ( this.ViewAngle > 160 ) this.ViewAngle = 160;
  this.camViewAngle = this.ViewAngle * toRad;
  this.FocalLength = 43.2 / ( 2 * Math.tan( this.camViewAngle / 2 ) );
  this.FocalLengthField = this.FocalLength;
  this.FocalLengthSlider = this.FocalLength;
  this.FocalLengthSliderLast = this.FocalLengthSlider;
  this.ViewAngleField = this.ViewAngle;
  this.ViewAngleSlider = this.ViewAngle;
  this.ViewAngleSliderLast = this.ViewAngle;

  // compute diverse values
  this.aDip = Math.acos( this.rEarth / (this.rEarth + this.Height) );
  this.aEarth = pi180 - 2 * this.aDip;
  this.rDisk = this.rEarth * Math.sin( this.aDip );
  this.dHorizon = this.rEarth * this.aDip;
  this.hDisk = this.rEarth * Math.cos( this.aDip );
  this.hDip = this.rEarth - this.hDisk;
  this.dView = ( this.Height + this.rEarth ) * Math.sin( this.aDip );
  this.aDelta = this.aDip / this.nLines;
  this.dDelta = this.showModel & 1 ? this.aDelta * this.rEarth : 0;
  this.posEarth = [ 0, 0, -(this.rEarth + this.Height) ];
  this.zDisk = (this.rEarth + this.Height) - ( this.rEarth * Math.cos( this.aDip ) );

  // compute camViewCenter from panning
  if ( Math.abs( this.Nick ) < 30 / 30 ) this.Nick = 0;
  if ( Math.abs( this.Roll ) < 30 / 30 ) this.Roll = 0;
  var dvc = Math.sqrt( this.rDisk * this.rDisk + this.zDisk * this.zDisk );
  var avc = this.aDip - this.Nick * toRad;
  if ( avc > pi90 ) avc = pi90;
  if ( avc < 0 ) avc = 0;
  var yvc = dvc * Math.cos( avc );
  var zvc = - dvc * Math.sin( avc );
  this.camViewCenter = [ 0, yvc, zvc ];

  // compute camera up and pos
  var a = this.Roll * toRad;
  this.camUp = [ Math.sin(a), 0.7, Math.cos(a) ];
  this.camPos = [ 0, 0, 0 ];

  // compute scene size taking device ratio into account
  var vpRatio = 3 / 2;
  var diag = 2 * this.dView * Math.tan( this.camViewAngle / 2 );
  this.sceneHeight = diag / Math.sqrt( 1 + this.deviceRatio*this.deviceRatio );
  this.sceneWidth = this.deviceRatio * this.sceneHeight;
  if ( this.deviceRatio > vpRatio ) {
    // device is wider then viewport
    this.camSceneSize = this.sceneWidth / vpRatio;
  } else {
    this.camSceneSize = this.sceneHeight;
  }

  // looking down
  var vpan = this.Nick * toRad / 2;
  this.drawBackGGrid = this.aDip > pi45 + vpan;
  this.drawBackFGrid = Math.atan( this.Height / this.rDisk ) > pi45 + vpan;
  this.drawBackFE    = Math.atan( this.Height / (2 * this.rEarth) ) > pi45 + vpan;
}

var Model = new EarthModel();

function UpdateAll() {
  Model.Update();
  ControlPanels.Update();
  graph.Redraw();
}

var graph = NewGraphX3D( {
  Id: 'JsGraph1',
  Width: '100%',
  Height: '66.67%',
  DrawFunc: DrawModel,
  AutoReset: true,
  AutoClear: true,
  AutoScalePix: true
} );

function DrawModel( g ) {

  g.SetAngleMeasure( 'rad' );
  g.SetViewport( 0, 1, -0.5, -2 );
  g.SetGraphClipping( true, '', 0 );
  g.SetWindowToCameraScreen();

  g.SetCamera( {
    SceneSize: Model.camSceneSize,
    CamPos: Model.camPos,
    CamUp: Model.camUp,
    CamViewCenter: Model.camViewCenter,
  } );
  g.SetCameraZoom( 1 );

  g.SetLineAttr( 'black', 2 );
  var xDir = JsgVect3.Mult( g.Camera.ViewDir, g.Camera.CamUp );
  var yDir = JsgVect3.Mult( g.Camera.ViewDir, xDir );
  g.SetPlane( g.Camera.CamViewCenter, xDir, yDir, true );
  g.RectOnPlane( -Model.sceneWidth/2, -Model.sceneHeight/2, Model.sceneWidth/2, Model.sceneHeight/2, 1 );

  // Globe Earth

  if ( Model.showModel & 1 ) {

    var alpha = 0.6 - 0.5 * (Math.log10( Model.Height ) / 9);
    g.SetAlpha( alpha );
    g.SetLineAttr( 'blue', 1 );

    // show globe grid
    if ( Model.showGrid & 1 ) {

      // latitude lines
      var latMax = Model.aDip;
      var latStart = -( Math.floor( latMax / Model.aDelta ) * Model.aDelta );
      if (!Model.drawBackGGrid) latStart = 0;
      for ( var lat = latStart; lat < latMax; lat += Model.aDelta ) {
        var dLatPlaneDisk = Model.hDisk / Math.cos( lat );
        var longMax = Math.acos( dLatPlaneDisk / Model.rEarth );
        var longStart = -( Math.floor( longMax / Model.aDelta ) * Model.aDelta );
        g.NewPoly();
        for ( var long = longStart; long < longMax; long += Model.aDelta ) {
          g.AddPointToPoly3D( PointOnEarth( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnEarth( lat, longMax ) );
        g.DrawPoly( 1 );
      }

      // longitude lines
      var longMax = Model.aDip;
      var longStart = -( Math.floor( longMax / Model.aDelta ) * Model.aDelta );
      for ( var long = longStart; long < longMax; long += Model.aDelta ) {
        var rLong = Model.rEarth * Math.cos( long );
        var latMax = Math.acos( Model.hDisk / rLong );
        var latStart = -( Math.floor( latMax / Model.aDelta ) * Model.aDelta );
        if (!Model.drawBackGGrid) latStart = 0;
        g.NewPoly();
        if (Model.drawBackGGrid) g.AddPointToPoly3D( PointOnEarth( -latMax, long ) );
        for ( var lat = latStart; lat < latMax; lat += Model.aDelta ) {
          g.AddPointToPoly3D( PointOnEarth( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnEarth( latMax, long ) );
        g.DrawPoly( 1 );
      }

    } // end show globe grid

    g.SetLineAttr( 'red', 1 );

    // show flat grid
    if ( Model.showGrid & 2 ) {

      // latitude lines on flat model
      var latMax = Model.aDip;
      var latStart = -( Math.floor( latMax / Model.aDelta ) * Model.aDelta );
      if (!Model.drawBackFGrid) latStart = 0;
      for ( var lat = latStart; lat < latMax; lat += Model.aDelta ) {
        var dLatPlaneDisk = Model.hDisk / Math.cos( lat );
        var longMax = Math.acos( dLatPlaneDisk / Model.rEarth );
        var longStart = -( Math.floor( longMax / Model.aDelta ) * Model.aDelta );
        g.NewPoly();
        for ( var long = longStart; long < longMax; long += Model.aDelta ) {
          g.AddPointToPoly3D( PointOnPlane( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnPlane( lat, longMax ) );
        g.DrawPoly( 1 );
      }

      // longitude lines on flat model
      var longMax = Model.aDip;
      var longStart = -( Math.floor( longMax / Model.aDelta ) * Model.aDelta );
      for ( var long = longStart; long < longMax; long += Model.aDelta ) {
        var rLong = Model.rEarth * Math.cos( long );
        var latMax = Math.acos( Model.hDisk / rLong );
        var latStart = -( Math.floor( latMax / Model.aDelta ) * Model.aDelta );
        if (!Model.drawBackFGrid) latStart = 0;
        g.NewPoly();
        if (Model.drawBackFGrid) g.AddPointToPoly3D( PointOnPlane( -latMax, long ) );
        for ( var lat = latStart; lat < latMax; lat += Model.aDelta ) {
          g.AddPointToPoly3D( PointOnPlane( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnPlane( latMax, long ) );
        g.DrawPoly( 1 );
      }

    } // end flat grid

    if ( (Model.showGrid & 2) || Model.showFlatHorizon ) {

      // horizon on flat model
      var aMax = Model.drawBackFGrid ? Math.PI * 2 : Math.PI;
      g.SetPlane( [ 0, 0, -Model.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
      g.SetAlpha( 1 );
      g.SetLineAttr( 'red', 2 );
      g.ArcOnPlane( 0, 0, Model.rDisk, 0, aMax, 1 );

    }

    if ( Model.showEquator ) {

      // equator
      var aMax = Model.drawBackFGrid ? Math.PI * 2 : Math.PI;
      g.SetPlane( [ 0, 0, -Model.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
      g.SetAlpha( 1 );
      g.SetLineAttr( 'black', 1 );
      g.ArcOnPlane( 0, 0, Model.rFEarth, 0, aMax, 1 );

    }

    // Globe Horizon
    var aMax = Model.drawBackGGrid ? Math.PI * 2 : Math.PI;
    g.SetAlpha( 1 );
    g.SetLineAttr( 'blue', 2 );
    g.SetPlane( [ 0, 0, -Model.zDisk ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.ArcOnPlane( 0, 0, Model.rDisk, 0, aMax, 1 );

  } // end model globe


  // Flat Earth

  if ( Model.showModel & 2 ) {

    var alpha = 0.6 - 0.5 * (Math.log10( Model.Height ) / 9);
    g.SetAlpha( alpha );
    g.SetLineAttr( 'black', 1 );

    // circle lines
    var aMax = Model.drawBackFE ? Math.PI * 2 : Math.PI;
    var crDelta = Model.rFEarth / 12;
    var crMax = 2 * Model.rFEarth - crDelta / 2;
    g.SetPlane( [ 0, 0, -Model.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
      g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
    }
    if (Model.Height < 700000) {
      crMax = crDelta;
      crDelta /= 10;
      crMax -= crDelta / 2;
      for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
        g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
      }
    }
    if (Model.Height < 30000) {
      crMax = crDelta;
      crDelta /= 10;
      crMax -= crDelta / 2;
      for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
        g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
      }
    }
    if (Model.Height < 3000) {
      crMax = crDelta;
      crDelta /= 10;
      crMax -= crDelta / 2;
      for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
        g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
      }
    }
    if (Model.Height < 300) {
      crMax = crDelta;
      crDelta /= 10;
      crMax -= crDelta / 2;
      for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
        g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
      }
    }
    g.SetAlpha( 1 );
    g.ArcOnPlane( 0, 0, Model.rFEarth, 0, aMax, 1 );
    g.SetLineAttr( 'black', 2 );
    g.ArcOnPlane( 0, 0, 2*Model.rFEarth, 0, aMax, 1 );

    // ray lines
    g.SetAlpha( alpha );
    g.SetLineAttr( 'black', 1 );
    var caDelta = Math.PI / 12;
    var caMax = Model.drawBackFE ? 2 * Math.PI : Math.PI;
    caMax -= caDelta / 2;
    var r = 2 * Model.rFEarth;
    for ( var ca = 0; ca < caMax; ca += caDelta ) {
      var c = Math.cos( ca );
      var s = Math.sin( ca );
      g.LineOnPlane( r * c, r * s, 0, 0 );
    }

  } // end nLines > 0

  if ( Model.showEyeLevel ) {
    g.SetLineAttr( 'magenta', 1 );
    g.SetAlpha( 1 );
    g.Line3D( [ -Model.sceneWidth/2, Model.rDisk, 0 ], [ Model.sceneWidth/2, Model.rDisk, 0 ] );
    g.SetTextAttr( 'Arial', 12, 'magenta', 'normal', 'normal', 'venter', 'bottom', 6 );
    g.SetTextRotation( -Model.Roll*Math.PI/180 );
    g.Text3D( 'Eye-Level', [ 0, Model.rDisk, 0 ] );
  }
}

function PointOnEarth( lat, long ) {
  var x = Model.rEarth * Math.sin( long );
  var rr = Model.rEarth * Math.cos( long );
  var y = rr * Math.sin( lat );
  var z = rr * Math.cos( lat );
  return [ x, y, z - (Model.rEarth + Model.Height) ];
}

function PointOnPlane( lat, long ) {
  var x = Model.rEarth * Math.sin( long );
  var rr = Model.rEarth * Math.cos( long );
  var y = rr * Math.sin( lat );
  return [ x, y, -Model.Height ];
}


ControlPanels.NewSliderPanel( {
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  Format: 'std',
  Digits: 3,
  ReadOnly: false,
  PanelFormat: 'InputMediumWidth'

} ).AddValueSliderField( {
  Name: 'Height',
  ValueRef: 'Height',
  SliderValueRef: 'HeightSlider',
  Mult: 1000,
  Units: 'km',
  Color: 'blue',
  Min: 0,
  Max: 1

} ).AddValueSliderField( {
  Name: 'ViewAngle',
  Label: 'View&ang;',
  ValueRef: 'ViewAngleField',
  SliderValueRef: 'ViewAngleSlider',
  Units: '&deg;',
  Color: 'black',
  Min: 5,
  Max: 90

} ).AddValueSliderField( {
  Name: 'FocalLength',
  ValueRef: 'FocalLengthField',
  SliderValueRef: 'FocalLengthSlider',
  Label: 'f',
  Units: 'mm',
  Color: 'black',
  Min: 21,
  Max: 500

} ).Render();


ControlPanels.NewSliderPanel( {
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  NCols: 2,
  Format: 'fix0',
  Digits: 0,
  ReadOnly: true,
  PanelFormat: 'InputMediumWidth'

} ).AddValueSliderField( {
  Name: 'Nick',
  Format: 'std',
  Digits: 3,
  Units: '&deg;',
  Color: 'green',
  Min: -45,
  Max: 30

} ).AddValueSliderField( {
  Name: 'Roll',
  Format: 'std',
  Digits: 3,
  Units: '&deg;',
  Color: 'green',
  Min: -45,
  Max: 45

} ).Render();


ControlPanels.NewPanel( {
  Name: 'Options',
  ModelRef: 'Model',
  NCols: 2,
  OnModelChange: UpdateAll

} ).AddRadiobuttonField( {
  Name: 'showModel',
  Label: 'Model',
  ValueType: 'int',
  Items: [
    {
      Name: 'Globe',
      Value: 1
    }, {
      Name: 'Globe+Flat',
      Value: 3
    }, {
      Name: 'Flat',
      Value: 2
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'HeightRange',
  ValueType: 'int',
  Items: [
    {
      Name: '50',
      Text: '50',
      Value: 50000
    }, {
      Name: '500',
      Text: '500',
      Value: 500000
    }, {
      Name: '20000',
      Text: '20&thinsp;000',
      Value: 20000000
    }, {
      Name: 'Log',
      Value: 0
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'showGrid',
  Label: 'Grid',
  ValueType: 'int',
  Items: [
    {
      Name: 'None',
      Value: 0
    }, {
      Name: 'Globe',
      Value: 1
    }, {
      Name: 'Globe+Flat',
      Value: 3
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'nLines',
  Label: 'Lines',
  ValueType: 'int',
  Items: [
    {
      Name: '15',
      Value: 15
    }, {
      Name: '30',
      Value: 30
    }, {
      Name: '45',
      Value: 45
    }, {
      Name: '60',
      Value: 60
    }, {
      Name: '90',
      Value: 90
    }
  ]

} ).AddCheckboxField( {
  Name: 'Show',
  Label: 'Show',
  Items: [
    {
      Name: 'showEyeLevel',
      Text: 'EyeLevel',
    }, {
      Name: 'showFlatHorizon',
      Text: 'FE-Horizon',
    }, {
      Name: 'showEquator',
      Text: 'FE-Equator',
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'deviceRatio',
  Label: 'AspectRatio',
  ValueType: 'num',
  Items: [
    {
      Name: '3:2',
      Value: 3/2
    }, {
      Name: '2:3',
      Value: 2/3
    }, {
      Name: '16:9',
      Value: 16/9
    }, {
      Name: '9:16',
      Value: 9/16
    }
  ]

} ).Render();


ControlPanels.NewPanel( {
  Name: 'Output',
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  NCols: 2,
  ReadOnly: true,
  Format: 'std',
  Digits: 4

} ).AddTextField( {
  Name: 'hDip',
  Label: 'DipHeight(b)',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'aDip',
  Label: 'DipAngle(&alpha;)',
  Mult: Math.PI / 180,
  Units: '&deg;'

} ).AddTextField( {
  Name: 'dHorizon',
  Label: 'HorDist(s)',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'aEarth',
  Label: 'AngDiameter',
  Mult: Math.PI / 180,
  Units: '&deg;'

} ).AddTextField( {
  Name: 'rDisk',
  Label: 'HorDistX(d)',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'dDelta',
  Label: 'LineSpacing',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'zDisk',
  Label: 'HorDistZ(p)',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'dView',
  Label: 'HorDistView(v)',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'rEarth',
  Label: 'RadiusPlanet',
  Mult: 1000,
  Units: 'km',
  ReadOnly: false

} ).Render();

</jscript>

Weitere Infos zur Seite
Erzeugt Dienstag, 31. Januar 2017
von wabis
Zum Seitenanfang
Geändert Mittwoch, 1. Februar 2017
von wabis