├── .gitignore ├── AspectAdaptive ├── UI.js ├── index.html └── projection-selection-list.css ├── CanvasMap - readme.txt ├── CanvasMap ├── CanvasMap-min.js ├── CanvasMap.js ├── build.xml ├── compiler.jar ├── css │ └── projection.css ├── jquery │ ├── css │ │ └── smoothness │ │ │ ├── images │ │ │ ├── animated-overlay.gif │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ │ ├── jquery-ui-1.10.2.custom.css │ │ │ └── jquery-ui-1.10.2.custom.min.css │ ├── jquery-1.11.1.min.js │ ├── jquery-ui-1.10.2.custom.js │ ├── jquery-ui-1.10.2.custom.min.js │ └── jquery.ui.touch-punch.min.js └── src │ ├── Graticule.js │ ├── Layer.js │ ├── LineDrawer.js │ ├── Map.js │ ├── binarywrapper.js │ ├── dbf.js │ ├── lib │ ├── binaryajax.js │ └── excanvas.js │ ├── projections │ ├── AlbersConic.js │ ├── Arden-Close.js │ ├── AspectAdaptiveCylindrical.js │ ├── Bonne.js │ ├── Braun2.js │ ├── Canters1.js │ ├── Canters2.js │ ├── CentralCylindrical.js │ ├── CylindricalStereographic.js │ ├── Eckert4.js │ ├── Eckert6.js │ ├── EqualAreaCylindrical.js │ ├── Equirectangular.js │ ├── Hammer.js │ ├── Hufnagel.js │ ├── Kavrayskiy1.js │ ├── Kavrayskiy5.js │ ├── KharchenkoShabanova.js │ ├── LambertAzimuthalEqualAreaPolar.js │ ├── LambertAzimuthalOblique.js │ ├── LambertEqualAreaCylindrical.js │ ├── McBrydeThomas1.js │ ├── McBrydeThomas2.js │ ├── Mercator.js │ ├── Miller.js │ ├── Miller2.js │ ├── MillerPerspective.js │ ├── MillerTransformation.js │ ├── Mollweide.js │ ├── NaturalEarth.js │ ├── Pavlov.js │ ├── PutninsP4P.js │ ├── Robinson.js │ ├── SineSeries.js │ ├── Sinusoidal.js │ ├── Strebe1995.js │ ├── Tobler1.js │ ├── Tobler2.js │ ├── TransformableLambert.js │ ├── TransverseTransformableLambert.js │ ├── Urmayev2.js │ ├── Urmayev3.js │ ├── Wagner1.js │ ├── Wagner4.js │ ├── Wagner7.js │ └── WagnerPseudocylindrical.js │ └── shapefile.js ├── CentralMeridianSlider ├── UI.js ├── index.html └── static.css ├── Hufnagel ├── UI.js ├── index.html └── projection-selection-list.css ├── LambertTransition ├── UI.js ├── index.html └── projection-selection-list.css ├── MillerTransformation ├── UI.js ├── index.html └── static.css ├── ProjectionList ├── UI.js └── index.html ├── README.md ├── SimpleMap ├── UI.js └── index.html ├── SineSeries ├── UI.js ├── index.html └── static.css ├── StrebeTransformation ├── UI.js ├── index.html └── static.css └── data ├── ne_110m_coastline.README.html ├── ne_110m_coastline.VERSION.txt ├── ne_110m_coastline.dbf ├── ne_110m_coastline.prj ├── ne_110m_coastline.shp └── ne_110m_coastline.shx /.gitignore: -------------------------------------------------------------------------------- 1 | # generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # thumbnails 9 | ._* 10 | 11 | # files that might appear on external disk 12 | .Spotlight-V100 13 | .Trashes 14 | 15 | # experimental stuff 16 | /CylindricalAdaptive/ 17 | /AlbersConic2LambertAzimuthal/ 18 | /LambertTransformationTransverse/ -------------------------------------------------------------------------------- /AspectAdaptive/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Layer, Graticule, $, AspectAdaptiveCylindrical, createMap */ 2 | 3 | var map; 4 | 5 | function deselectButtons() { 6 | "use strict"; 7 | $('#selectable .ui-selected').removeClass('ui-selected'); 8 | $('#selectable .ui-selecting').removeClass('ui-selecting'); 9 | } 10 | 11 | function updateProjection() { 12 | "use strict"; 13 | var projection = new AspectAdaptiveCylindrical(); 14 | projection.setAspectRatio($("#sliderAspect").slider("value") / 100); 15 | map.setProjection(projection); 16 | } 17 | 18 | function updateSliderTexts() { 19 | "use strict"; 20 | var aspectRatio; 21 | aspectRatio = $("#sliderAspect").slider("value") / 100; 22 | $("#sliderAspectText").text(aspectRatio.toFixed(2)); 23 | } 24 | 25 | function setProjection(selectedItem) { 26 | "use strict"; 27 | 28 | var aspectRatio; 29 | 30 | switch (selectedItem) { 31 | case "projectionPlateCarree": 32 | aspectRatio = 0.5; 33 | break; 34 | case "projectionCompactMiller": 35 | aspectRatio = 0.6; 36 | break; 37 | } 38 | 39 | if (!isNaN(aspectRatio)) { 40 | $("#sliderAspect").slider("value", aspectRatio * 100); 41 | } 42 | 43 | updateSliderTexts(); 44 | updateProjection(); 45 | } 46 | 47 | $(function() { 48 | "use strict"; 49 | 50 | var mousedown = false, firstRun = true, last_selectedItem_id; 51 | $('#selectable').selectable({ 52 | start : function(event, ui) { 53 | mousedown = true; 54 | }, 55 | stop : function(event, ui) { 56 | //Here the event ends, so that we can remove the selected class to all but the one we want 57 | $(event.target).children('.ui-selected').not("#" + last_selectedItem_id).removeClass('ui-selected'); 58 | mousedown = false; 59 | }, 60 | //a special case is the first run, 61 | //$(".ui-selected, .ui-selecting").length is considered to be 2 62 | selecting : function(event, ui) { 63 | if (($(".ui-selected, .ui-selecting").length > 1 ) && firstRun === false) { 64 | $(event.target).children('.ui-selecting').not(':first').removeClass('ui-selecting'); 65 | $(ui.selecting).removeClass("ui-selecting"); 66 | } else { 67 | setProjection(ui.selecting.id); 68 | last_selectedItem_id = ui.selecting.id; 69 | if (firstRun) { 70 | firstRun = false; 71 | } 72 | } 73 | } 74 | }); 75 | }); 76 | 77 | function mouseEventHandler(e) { 78 | "use strict"; 79 | 80 | var mouseMove, mouseUp, prevMouse; 81 | 82 | prevMouse = { 83 | x : e.clientX, 84 | y : e.clientY 85 | }; 86 | 87 | mouseMove = function(e) { 88 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 89 | 90 | unitsPerPixel = 1 / map.getScale(); 91 | lon0 = map.getCentralLongitude(); 92 | lon0 -= dx * unitsPerPixel; 93 | map.setCentralLongitude(lon0); 94 | 95 | prevMouse.x = e.clientX; 96 | prevMouse.y = e.clientY; 97 | e.preventDefault(); 98 | }; 99 | 100 | mouseUp = function(e) { 101 | document.body.style.cursor = null; 102 | document.removeEventListener('mousemove', mouseMove, false); 103 | document.removeEventListener('mouseup', mouseUp, false); 104 | }; 105 | 106 | document.body.style.cursor = 'move'; 107 | document.addEventListener('mousemove', mouseMove, false); 108 | document.addEventListener('mouseup', mouseUp, false); 109 | } 110 | 111 | function initAspectSlider() { 112 | "use strict"; 113 | function action(event, ui) { 114 | // fix a bug in jQuery slider 115 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 116 | $("#sliderAspect").slider('value', ui.value); 117 | updateSliderTexts(); 118 | updateProjection(); 119 | deselectButtons(); 120 | } 121 | 122 | // create the slider 123 | $("#sliderAspect").slider({ 124 | orientation : "horizontal", 125 | range : "min", 126 | min : 30, 127 | max : 100, 128 | value : 60, 129 | step : 1, 130 | slide : action 131 | }); 132 | } 133 | 134 | function getMapScale(map) { 135 | "use strict"; 136 | 137 | var K = 0.95/2, aspectRatio = $("#sliderAspect").slider("value") / 100; 138 | /* 139 | if (aspectRatio > map.getCanvas().height / map.getCanvas().width) { 140 | return map.getCanvas().height / (Math.PI) * K; 141 | } 142 | return map.getCanvas().width / Math.PI * K;*/ 143 | 144 | if (map.getCanvas().height > map.getCanvas().width) { 145 | return map.getCanvas().width / Math.PI * K; 146 | } 147 | return map.getCanvas().height / Math.PI * K 148 | } 149 | 150 | 151 | $(window).load(function() { 152 | "use strict"; 153 | // currently styling works like this: 154 | // - if there's a fillStyle then it will be filled 155 | // - if there's a strokeStyle then it will be stroked 156 | // - points are always 3px rectangles 157 | // - polylines can't be filled 158 | 159 | var layers, graticule; 160 | 161 | function Style(strokeStyle, lineWidth, fillStyle) { 162 | if (strokeStyle !== undefined) { 163 | this.strokeStyle = strokeStyle; 164 | this.lineWidth = lineWidth; 165 | } 166 | if (fillStyle !== undefined) { 167 | this.fillStyle = fillStyle; 168 | } 169 | } 170 | 171 | //create the map 172 | graticule = new Graticule(new Style("#77b", "1"), 15); 173 | layers = []; 174 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 175 | layers.push(graticule); 176 | 177 | map = createMap(layers, new AspectAdaptiveCylindrical(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 178 | 179 | $(window).resize(function() { 180 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 181 | map.setScale(getMapScale(map)); 182 | }); 183 | 184 | $("#map").bind("mousedown", mouseEventHandler); 185 | 186 | initAspectSlider(); 187 | updateSliderTexts(); 188 | updateProjection(); 189 | map.setScale(getMapScale(map)); 190 | }); 191 | -------------------------------------------------------------------------------- /AspectAdaptive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aspect-Adaptive Cylindrical 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

A Compromise Aspect-Adaptive Cylindrical Projection

24 |

25 | A new compromise cylindrical map projection family that adjusts the distribution of parallels to the aspect ratio of a canvas. 26 |

27 |

28 | Developed by Bernhard Jenny (Oregon State University), Bojan Šavrič (Oregon State University), and Tom Patterson (US National Park Service). 29 |

30 |

31 | Jenny, B., Šavrič, B. and Patterson, T. (—).
A compromise aspect-adaptive cylindrical projection for world maps.
International Journal of Geographical Information Science (in press).

32 |

33 | 34 |
35 | 36 |
37 |
38 | Length Ratio between Equator and Central Meridian 39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 |
    47 |
  1. 48 | Plate Carrée 49 |
  2. 50 |
  3. 51 | Compact Miller 52 |
  4. 53 |
54 | 55 |
56 | 57 |
58 | 59 |
60 |
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /AspectAdaptive/projection-selection-list.css: -------------------------------------------------------------------------------- 1 | #foldingWarning { 2 | color:red; 3 | margin-top: 12px; 4 | margin: 6px; 5 | display: none; 6 | } 7 | 8 | #StParallel { 9 | margin-bottom: 30px; 10 | } 11 | 12 | #selectable { 13 | list-style-type: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | #feedback { 18 | font-size: 1.4em; 19 | } 20 | #selectable .ui-selecting { 21 | background: #BBBBBB; 22 | } 23 | #selectable .ui-selected { 24 | background: #BBBBBB; 25 | color: white; 26 | } 27 | #selectable li { 28 | margin-top: 3px; 29 | margin-bottom: 3px; 30 | padding: 0.4em; 31 | width: 195px; 32 | height: 18px; 33 | } -------------------------------------------------------------------------------- /CanvasMap - readme.txt: -------------------------------------------------------------------------------- 1 | CanvasMap 2 | ========= 3 | 4 | CanvasMap is a simple HTML framework to experiment with projection equations. 5 | CanvasMap uses JavaScript, HTML5 Canvas, and JQuery. 6 | 7 | Programming by Bernhard Jenny and Bojan Savric, College of Earth, Ocean and 8 | Atmospheric Sciences, Oregon State University, and Mostafa El-Fouly, TU Munich. 9 | 10 | Contact: Bernhard Jenny 11 | 12 | License: GNU General Public License Version 2: http://www.gnu.org/licenses/gpl-2.0.html 13 | 14 | File structure 15 | ============== 16 | 17 | CanvasMap contains the core JavaScript files. Code related to the map and projections is 18 | in src/. CanvasMap.js is created by the build.xml ant script. Include this file in the 19 | HTML file. 20 | 21 | Demos 22 | ===== 23 | 24 | All code specific to an application is included in the UI.js files. These files construct 25 | the HTML GUI, provide event handlers and create the map with a projection. 26 | 27 | SimpleMap shows a map with a static projection. 28 | CentralMeridianSlider shows a map and slider to adjust the central meridian. 29 | ProjectionList has a menu to select from various projections. 30 | LambertTransitionSlider illustrates Wagner's transformation applied to the Lambert 31 | azimuthal projection to create a variety of equal-area projections. 32 | MillerTransformation illustrates a transformation of the Mercator projection suggested 33 | by Miller in 1942. 34 | StrebeTransformation illustrates a transformation method invented by D. Strebe. 35 | 36 | Programming Model 37 | ================= 38 | 39 | UI.js: 40 | DOM event handlers and construction of the model consisting of a map, one or more layers 41 | and a projection. 42 | 43 | layer.js: 44 | A layer loads a shapefile (points, polylines or polygons), and projects and draws the data. 45 | Polygons are not filled. Shapefiles must use 'geographical' coordinates. 46 | 47 | map.js: 48 | Contains an array of layers, a projection, and scale factor applied before drawing the map. 49 | After the layer, canvas, projection and scale are created, invoke map.load to load the 50 | layer geometry. 51 | 52 | Extending CanvasMap with Custom Projections 53 | =========================================== 54 | 55 | To extend CanvasMap with an additional projection, duplicate an existing 56 | projection file in the projections folder and change the function name and the included 57 | toString() and forward() functions. 58 | 59 | The forward function receives longitude and latitude values in radians. The projected 60 | coordinates have to be written to the xy array. The xy parameter is only for returning the 61 | projected coordinates and does not contain valid data when the function is called. 62 | 63 | 64 | Important: use Apache Ant to concatenate and minify all JavaScript files in the src folder. 65 | The default Ant target creates CanvasMap.js and CanvasMap-min.js. To run Ant, 66 | cd to CanvasMap, then type ant. 67 | 68 | To apply the new projection to the map, change UI.js. 69 | If you use the "ProjectionList" map, add your projection to the getProjection function in 70 | UI.js. Also include your projection in the element has to be unique and match the projectionName 72 | parameter of getProjection in UI.js. -------------------------------------------------------------------------------- /CanvasMap/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | /* Build time: ${build.time} */ 20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
-------------------------------------------------------------------------------- /CanvasMap/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/compiler.jar -------------------------------------------------------------------------------- /CanvasMap/css/projection.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eee; 3 | font: 12px sans-serif; 4 | } 5 | 6 | h1 { 7 | font: 18px sans-serif; 8 | } 9 | 10 | #container { 11 | } 12 | 13 | .map { 14 | height:700px; 15 | padding:0px; 16 | border:0px; 17 | background-color: #fff; 18 | margin-right: 310px; 19 | position:relative; 20 | } 21 | 22 | .mapCanvas { 23 | width: 100%; 24 | height: 100%; 25 | position:absolute; 26 | } 27 | 28 | .mapControls { 29 | width: 300px; 30 | float: right; 31 | margin-left: 10px; 32 | } 33 | 34 | .sliderContainer { 35 | padding-top: 5px; 36 | padding-bottom: 30px; 37 | width: 300px; 38 | } 39 | 40 | .slider { 41 | float: left; 42 | width: 200px; 43 | } 44 | 45 | .sliderValueText { 46 | float: right; 47 | width: 80px; 48 | } 49 | 50 | .menu { 51 | width: 300px; 52 | margin-bottom: 10px; 53 | } -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/animated-overlay.gif -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/jquery/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /CanvasMap/jquery/css/smoothness/jquery-ui-1.10.2.custom.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.2 - 2013-04-10 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.core.css, jquery.ui.slider.css 4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px 5 | * Copyright 2013 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} -------------------------------------------------------------------------------- /CanvasMap/jquery/jquery.ui.touch-punch.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Touch Punch 0.2.2 3 | * 4 | * Copyright 2011, Dave Furfero 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Depends: 8 | * jquery.ui.widget.js 9 | * jquery.ui.mouse.js 10 | */ 11 | (function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return;}var c=b.ui.mouse.prototype,e=c._mouseInit,a;function d(g,h){if(g.originalEvent.touches.length>1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery); -------------------------------------------------------------------------------- /CanvasMap/src/Graticule.js: -------------------------------------------------------------------------------- 1 | /*globals LineDrawer, applyStyle*/ 2 | 3 | /** 4 | * Map graticule (lines of constant longitude and latitude) 5 | * @param {Object} style Style for drawing the graticule 6 | * @param {Object} lineDistanceDeg Distance in degrees between neighboring lines of constant longitude or latitude. 7 | * @param {Object} maxPointDistance Maximum distance in degrees for drawing lines. Optional. Specify low value (e.g., 1 deg) if graticule is unusually curvy. 8 | */ 9 | function Graticule(style, lineDistanceDeg, maxPointDistance) { 10 | "use strict"; 11 | 12 | if (maxPointDistance === undefined) { 13 | maxPointDistance = 5; 14 | } 15 | var nVerticalPoints = 180 / Math.min(lineDistanceDeg, maxPointDistance) + 1, 16 | nHorizontalPoints = 360 / Math.min(lineDistanceDeg, maxPointDistance) + 1; 17 | 18 | function drawMeridian(projection, scale, lineDrawer, lon) { 19 | var i, 20 | lat, 21 | dLat = Math.PI / (nVerticalPoints - 1); 22 | for ( i = 0; i < nVerticalPoints; i += 1) { 23 | lat = -Math.PI / 2 + i * dLat; 24 | lineDrawer.projectDraw(lon, lat); 25 | } 26 | lineDrawer.stroke(); 27 | } 28 | 29 | function drawParallel(projection, scale, lineDrawer, lat) { 30 | var i, 31 | lon, 32 | dLon = 2 * Math.PI / (nHorizontalPoints - 1); 33 | for ( i = 0; i < nHorizontalPoints; i += 1) { 34 | lon = -Math.PI + i * dLon; 35 | lineDrawer.projectDraw(lon, lat); 36 | } 37 | lineDrawer.stroke(); 38 | } 39 | 40 | 41 | this.render = function(projection, lon0, scale, canvas) { 42 | var i, 43 | lon, 44 | lat, 45 | ctx = canvas.getContext('2d'), 46 | lineDrawer = new LineDrawer(0, scale, projection, ctx); 47 | 48 | ctx.save(); 49 | ctx.translate(canvas.width / 2, canvas.height / 2); 50 | applyStyle(style, ctx); 51 | 52 | // meridians 53 | for ( i = 0; i < 360 / lineDistanceDeg; i += 1) { 54 | lon = -Math.PI + i * lineDistanceDeg / 180 * Math.PI - lon0; 55 | while (lon < -Math.PI) { 56 | lon += Math.PI * 2; 57 | } 58 | while (lon > Math.PI) { 59 | lon -= Math.PI * 2; 60 | } 61 | drawMeridian(projection, scale, lineDrawer, lon); 62 | } 63 | 64 | // parallels 65 | for ( i = 1; i < 180 / lineDistanceDeg; i += 1) { 66 | lat = -Math.PI / 2 + i * lineDistanceDeg / 180 * Math.PI; 67 | drawParallel(projection, scale, lineDrawer, lat); 68 | } 69 | 70 | // vertical graticule border 71 | if ( typeof (projection.isPoleInsideGraticule) !== 'function' || projection.isPoleInsideGraticule() === false) { 72 | drawMeridian(projection, scale, lineDrawer, -Math.PI); 73 | drawMeridian(projection, scale, lineDrawer, Math.PI); 74 | } 75 | 76 | // horizontal graticule border 77 | drawParallel(projection, scale, lineDrawer, -Math.PI / 2); 78 | drawParallel(projection, scale, lineDrawer, Math.PI / 2); 79 | 80 | ctx.restore(); 81 | }; 82 | 83 | this.load = function(projection, scale, map) { 84 | // dummy 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /CanvasMap/src/Layer.js: -------------------------------------------------------------------------------- 1 | /*global LineDrawer, ShpType, ShpFile, DbfFile, BinaryAjax, applyStyle */ 2 | 3 | function Layer(url, style) {"use strict"; 4 | 5 | var onShpFail, onShpComplete, onDbfFail, onDbfComplete, pointD = 4, layer = this; 6 | 7 | this.shpFile = null; 8 | this.dbfFile = null; 9 | 10 | function adjustLongitude(lon, lon0) { 11 | lon -= lon0; 12 | while (lon < -Math.PI) { 13 | lon += Math.PI * 2; 14 | } 15 | while (lon > Math.PI) { 16 | lon -= Math.PI * 2; 17 | } 18 | return lon; 19 | } 20 | 21 | function renderPoints(projection, lon0, scale, canvas) { 22 | 23 | var ctx, sc, recordID, shapeRecord, shape, projectionPoint, lon, lat, x, y, dx, dy, fill, stroke; 24 | 25 | projectionPoint = []; 26 | dx = canvas.width / 2; 27 | dy = -canvas.height / 2; 28 | ctx = canvas.getContext('2d'); 29 | ctx.save(); 30 | fill = typeof style !== "undefined" && style.hasOwnProperty("fillStyle"); 31 | stroke = typeof style !== "undefined" && style.hasOwnProperty("strokeStyle"); 32 | 33 | if (fill && ( typeof style.fillStyle !== "undefined")) { 34 | ctx.fillStyle = style.fillStyle; 35 | } 36 | if (stroke && ( typeof style.strokeStyle !== "undefined")) { 37 | ctx.strokeStyle = style.strokeStyle; 38 | } 39 | if ( typeof style !== "undefined" && style.hasOwnProperty("lineWidth") && typeof style.lineWidth !== "undefined") { 40 | ctx.lineWidth = style.lineWidth; 41 | } 42 | 43 | for ( recordID = 0; recordID < layer.shpFile.records.length; recordID += 1) { 44 | shapeRecord = layer.shpFile.records[recordID]; 45 | shape = shapeRecord.shape; 46 | 47 | lon = adjustLongitude(shape.x, lon0); 48 | lat = shape.y; 49 | projection.forward(lon, lat, projectionPoint); 50 | x = projectionPoint[0] * scale + dx; 51 | y = canvas.height - projectionPoint[1] * scale + dy; 52 | 53 | if (fill && typeof style.fillStyle !== 'undefined') { 54 | ctx.fillRect(x - pointD / 2, y - pointD / 2, pointD, pointD); 55 | } 56 | if (stroke && typeof style.strokeStyle !== 'undefined') { 57 | ctx.strokeRect(x - pointD / 2, y - pointD / 2, pointD, pointD); 58 | } 59 | } 60 | ctx.restore(); 61 | } 62 | 63 | function renderPolygons(projection, lon0, scale, canvas) { 64 | 65 | var ctx, sc, shapeRecord, recordID, shape, ringID, ring, ptID, lineDrawer; 66 | 67 | ctx = canvas.getContext('2d'); 68 | ctx.save(); 69 | ctx.translate(canvas.width / 2, canvas.height / 2); 70 | applyStyle(style, ctx); 71 | lineDrawer = new LineDrawer(lon0, scale, projection, ctx); 72 | 73 | for ( recordID = 0; recordID < layer.shpFile.records.length; recordID += 1) { 74 | shapeRecord = layer.shpFile.records[recordID]; 75 | shape = shapeRecord.shape; 76 | for ( ringID = 0; ringID < shape.rings.length; ringID += 1) { 77 | ring = shape.rings[ringID]; 78 | for ( ptID = 0; ptID < ring.length; ptID += 1) { 79 | lineDrawer.intersectProjectDraw(ring[ptID].x, ring[ptID].y); 80 | } 81 | lineDrawer.stroke(); 82 | } 83 | } 84 | ctx.restore(); 85 | } 86 | 87 | 88 | this.render = function(projection, lon0, scale, canvas) { 89 | 90 | if (layer.shpFile === null) { 91 | return; 92 | } 93 | 94 | switch (layer.shpFile.header.shapeType) { 95 | case ShpType.SHAPE_POLYGON: 96 | case ShpType.SHAPE_POLYLINE: 97 | renderPolygons(projection, lon0, scale, canvas); 98 | break; 99 | case ShpType.SHAPE_POINT: 100 | renderPoints(projection, lon0, scale, canvas); 101 | break; 102 | } 103 | }; 104 | 105 | function pointsDegreeToRadian(records) { 106 | var i, nRecords, c = Math.PI / 180, shp; 107 | for ( i = 0, nRecords = records.length; i < nRecords; i += 1) { 108 | shp = records[i].shape; 109 | shp.x *= c; 110 | shp.y *= c; 111 | } 112 | } 113 | 114 | function linesDegreeToRadian(records) { 115 | var c = Math.PI / 180, nRecords, nRings, nVertices, i, j, ring, k, lon, lat, xMin, xMax, yMin, yMax, shp; 116 | for ( i = 0, nRecords = records.length; i < nRecords; i += 1) { 117 | shp = records[i].shape; 118 | xMin = Number.MAX_VALUE; 119 | xMax = -Number.MAX_VALUE; 120 | yMin = Number.MAX_VALUE; 121 | yMax = -Number.MAX_VALUE; 122 | 123 | for ( j = 0, nRings = shp.rings.length; j < nRings; j += 1) { 124 | ring = shp.rings[j]; 125 | for ( k = 0, nVertices = ring.length; k < nVertices; k += 1) { 126 | lon = ring[k].x * c; 127 | 128 | if (lon > xMax) { 129 | xMax = lon; 130 | } 131 | if (lon < xMin) { 132 | xMin = lon; 133 | } 134 | 135 | if (lon > Math.PI) { 136 | lon = Math.PI; 137 | } else if (lon < -Math.PI) { 138 | lon = -Math.PI; 139 | } 140 | 141 | ring[k].x = lon; 142 | 143 | lat = ring[k].y * c; 144 | 145 | // clamp to +/-PI/2 146 | if (lat > Math.PI / 2) { 147 | lat = Math.PI / 2; 148 | } else if (lat < -Math.PI / 2) { 149 | lat = -Math.PI / 2; 150 | } 151 | 152 | if (lat > yMax) { 153 | yMax = lat; 154 | } 155 | if (lat < yMin) { 156 | yMin = lat; 157 | } 158 | ring[k].y = lat; 159 | } 160 | } 161 | shp.box.xMin = xMin; 162 | shp.box.xMax = xMax; 163 | shp.box.yMin = yMin; 164 | shp.box.yMax = yMax; 165 | } 166 | } 167 | 168 | 169 | this.load = function(map) { 170 | 171 | function onShpFail() { 172 | alert('Failed to load ' + url); 173 | } 174 | 175 | function onDbfFail() { 176 | alert('Failed to load ' + url); 177 | } 178 | 179 | function onShpComplete(oHTTP) { 180 | layer.shpFile = new ShpFile(oHTTP.binaryResponse); 181 | 182 | // convert geometry from degrees to radians 183 | switch (layer.shpFile.header.shapeType) { 184 | case ShpType.SHAPE_POLYGON: 185 | case ShpType.SHAPE_POLYLINE: 186 | linesDegreeToRadian(layer.shpFile.records); 187 | break; 188 | case ShpType.SHAPE_POINT: 189 | pointsDegreeToRadian(layer.shpFile.records); 190 | break; 191 | } 192 | 193 | if (layer.dbfFile !== null) { 194 | map.render(); 195 | } 196 | } 197 | 198 | function onDbfComplete(oHTTP) { 199 | layer.dbfFile = new DbfFile(oHTTP.binaryResponse); 200 | if (layer.shpFile !== null) { 201 | map.render(); 202 | } 203 | } 204 | 205 | var shpLoader, dbfLoader; 206 | try { 207 | shpLoader = new BinaryAjax(url + '.shp', onShpComplete, onShpFail); 208 | dbfLoader = new BinaryAjax(url + '.dbf', onDbfComplete, onDbfFail); 209 | } catch (e) { 210 | alert(e); 211 | } 212 | }; 213 | } 214 | -------------------------------------------------------------------------------- /CanvasMap/src/LineDrawer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Square distance between a point and a line defined by two other points. 3 | * See http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html 4 | * @param x0 The point not on the line. 5 | * @param y0 The point not on the line. 6 | * @param x1 A point on the line. 7 | * @param y1 A point on the line. 8 | * @param x2 Another point on the line. 9 | * @param y2 Another point on the line. 10 | */ 11 | function pointLineDistanceSquare(x0, y0, x1, y1, x2, y2) {"use strict"; 12 | var d, x2_x1 = x2 - x1, y2_y1 = y2 - y1; 13 | d = (x2_x1) * (y1 - y0) - (x1 - x0) * (y2_y1); 14 | return d * d / (x2_x1 * x2_x1 + y2_y1 * y2_y1); 15 | } 16 | 17 | function adjustLongitude(lon, lon0) {"use strict"; 18 | lon -= lon0; 19 | while (lon < -Math.PI) { 20 | lon += Math.PI * 2; 21 | } 22 | while (lon > Math.PI) { 23 | lon -= Math.PI * 2; 24 | } 25 | return lon; 26 | } 27 | 28 | function SphericalRotation(poleLat) {"use strict"; 29 | var sinLatPole, cosLatPole; 30 | 31 | sinLatPole = Math.sin(poleLat); 32 | cosLatPole = Math.cos(poleLat); 33 | 34 | this.getPoleLat = function() { 35 | return poleLat; 36 | }; 37 | 38 | this.transform = function(lon, lat, res) { 39 | var sinLon, cosLon, sinLat, cosLat, cosLat_x_cosLon; 40 | sinLon = Math.sin(lon); 41 | cosLon = Math.cos(lon); 42 | sinLat = Math.sin(lat); 43 | cosLat = Math.cos(lat); 44 | /* 45 | // FIXME transverse 46 | res[0] = Math.atan2(cosLat * sinLon, sinLat); 47 | res[1] = Math.asin(-cosLat * cosLon); 48 | */ 49 | cosLat_x_cosLon = cosLat * cosLon; 50 | res[0] = Math.atan2(cosLat * sinLon, sinLatPole * cosLat_x_cosLon + cosLatPole * sinLat); 51 | sinLat = sinLatPole * sinLat - cosLatPole * cosLat_x_cosLon; 52 | res[1] = Math.asin(sinLat); 53 | }; 54 | 55 | this.transformInv = function(lon, lat, res) { 56 | var sinLon = Math.sin(lon), cosLon = Math.cos(lon), sinLat = Math.sin(lat), cosLat = Math.cos(lat); 57 | var cosLat_x_cosLon = cosLat * cosLon; 58 | res[0] = Math.atan2(cosLat * sinLon, sinLatPole * cosLat_x_cosLon - cosLatPole * sinLat); 59 | res[1] = Math.asin(sinLatPole * sinLat + cosLatPole * cosLat_x_cosLon); 60 | }; 61 | 62 | } 63 | 64 | function LineDrawer(lon0, scale, projection, ctx) {"use strict"; 65 | 66 | var prevX, prevY, prevLon, prevLat, prevPointOutOfRange, moveTo = true, CURVE_TOL, CURVE_TOL_SQR; 67 | 68 | var sphericalRotation = new SphericalRotation(Math.PI / 4); 69 | 70 | // the tolerance by which a line may deviate from a perfectly smooth line 71 | CURVE_TOL = 0.25 / scale; 72 | CURVE_TOL_SQR = CURVE_TOL * CURVE_TOL; 73 | 74 | function drawCmd(lon, lat, lon0, stackDepth) { 75 | var lonMean, latMean, x, y, dsq, xy = [], xyMean = []; 76 | 77 | if ((stackDepth += 1) > 50) { 78 | return; 79 | } 80 | 81 | lon = adjustLongitude(lon, lon0); 82 | 83 | /*var rot = []; 84 | sphericalRotation.transform(lon, lat, rot); 85 | lon = rot[0]; 86 | lat = rot[1]; 87 | */ 88 | projection.forward(lon, lat, xy); 89 | 90 | if (moveTo) { 91 | if (isFinite(xy[0]) && isFinite(xy[1])) { 92 | ctx.beginPath(); 93 | ctx.moveTo(xy[0] * scale, -xy[1] * scale); 94 | moveTo = false; 95 | } 96 | } else { 97 | // compute the orthogonal distance in Cartesian coordinates of the mean point to the line 98 | // between the start and the end point 99 | lonMean = (prevLon + lon) * 0.5; 100 | latMean = (prevLat + lat) * 0.5; 101 | projection.forward(lonMean, latMean, xyMean); 102 | dsq = pointLineDistanceSquare(xyMean[0], xyMean[1], prevX, prevY, xy[0], xy[1]); 103 | 104 | if (isFinite(dsq)) { 105 | // if the distance is too large, add intermediate points 106 | if (dsq > CURVE_TOL_SQR) { 107 | drawCmd(lonMean, latMean, 0, stackDepth); 108 | drawCmd(lon, lat, 0, stackDepth); 109 | } 110 | 111 | ctx.lineTo(xy[0] * scale, -xy[1] * scale); 112 | } else { 113 | ctx.stroke(); 114 | moveTo = true; 115 | } 116 | } 117 | prevX = xy[0]; 118 | prevY = xy[1]; 119 | prevLon = lon; 120 | prevLat = lat; 121 | } 122 | 123 | 124 | this.stroke = function() { 125 | ctx.stroke(); 126 | moveTo = true; 127 | }; 128 | 129 | /** 130 | * Computes two intersection points for a straight line segment that crosses 131 | * the anti-meridian. Projects and draws the two intersection points and 132 | * the end point. 133 | * @param lonEnd The longitude of the end point of the line segment. 134 | * @param latEnd The latitude of the end point of the line segment. 135 | * @param lonStart The longitude of the start point of the line segment. 136 | * @param latStart The latitude of the start point of the line segment. 137 | */ 138 | this.projectIntersectingLineTo = function(lonEnd, latEnd, lonStart, latStart) { 139 | 140 | // lon1 the longitude of the intermediate end point 141 | // lon2 the longitude of the intermediate start point 142 | // lat the latitude of both intermediate points 143 | var dLon, dLat, lonMax, lonMin, lon1, lon2, lat; 144 | dLon = lonEnd - lonStart; 145 | dLat = latEnd - latStart; 146 | 147 | // compute intersection point in geographic coordinates 148 | lonMax = Math.PI + lon0; 149 | lonMin = -Math.PI + lon0; 150 | 151 | if (lonEnd > lonMax) {// leaving graticule towards east 152 | lon1 = Math.PI; 153 | lat = latStart + dLat * (lonMax - lonStart) / dLon; 154 | lon2 = -Math.PI; 155 | } else if (lonStart > lonMax) {// entering graticule from east 156 | lon1 = -Math.PI; 157 | lat = latStart + dLat * (lonMax - lonStart) / dLon; 158 | lon2 = Math.PI; 159 | } else if (lonEnd < lonMin) {// leaving graticule towards west 160 | lon1 = -Math.PI; 161 | lat = latStart + dLat * (lonMin - lonStart) / dLon; 162 | lon2 = Math.PI; 163 | } else if (lonStart < lonMin) {// entering graticule from west 164 | lon1 = Math.PI; 165 | lat = latStart + dLat * (lonMin - lonStart) / dLon; 166 | lon2 = -Math.PI; 167 | } 168 | drawCmd(lon1, lat, 0, 1); 169 | this.stroke(); 170 | drawCmd(lon2, lat, 0, 1); 171 | drawCmd(lonEnd, latEnd, lon0, 1); 172 | }; 173 | 174 | this.projectDraw = function(lon, lat) { 175 | drawCmd(lon, lat, 0, 1); 176 | }; 177 | 178 | this.intersectProjectDraw = function(lon, lat) { 179 | var pointOutOfRange; 180 | 181 | if (moveTo) { 182 | prevPointOutOfRange = pointOutOfRange = lon - lon0 < -Math.PI || lon - lon0 > Math.PI; 183 | } else { 184 | pointOutOfRange = lon - lon0 < -Math.PI || lon - lon0 > Math.PI; 185 | } 186 | 187 | if (prevPointOutOfRange !== pointOutOfRange) { 188 | prevPointOutOfRange = pointOutOfRange; 189 | this.projectIntersectingLineTo(lon, lat, prevLon, prevLat); 190 | } else { 191 | drawCmd(lon, lat, lon0, 1); 192 | } 193 | }; 194 | } -------------------------------------------------------------------------------- /CanvasMap/src/Map.js: -------------------------------------------------------------------------------- 1 | // http://www.html5rocks.com/en/tutorials/canvas/hidpi/ 2 | function backingScale(ctx) {"use strict"; 3 | var devicePixelRatio, backingStoreRatio, ratio, context; 4 | devicePixelRatio = window.devicePixelRatio || 1; 5 | backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; 6 | ratio = devicePixelRatio / backingStoreRatio; 7 | return ratio; 8 | } 9 | 10 | function applyStyle(style, ctx) {"use strict"; 11 | var s, value; 12 | 13 | if ( typeof style !== "undefined") { 14 | for (s in style) { 15 | if (style.hasOwnProperty(s)) { 16 | value = style[s]; 17 | if (s === 'lineWidth') { 18 | value *= backingScale(ctx); 19 | } 20 | ctx[s] = value; 21 | } 22 | } 23 | } 24 | } 25 | 26 | function Map(layers, canvas) {"use strict"; 27 | 28 | var projection, draw = true, lon0 = 0, scale = 1; 29 | 30 | projection = { 31 | // equirectangular projection 32 | forward : function(lon, lat, xy) { 33 | xy[0] = lon; 34 | xy[1] = lat; 35 | } 36 | }; 37 | 38 | this.load = function() { 39 | var i; 40 | for ( i = 0; i < layers.length; i += 1) { 41 | layers[i].load(this); 42 | } 43 | }; 44 | 45 | this.getCanvas = function() { 46 | return canvas; 47 | }; 48 | 49 | this.getProjection = function() { 50 | return projection; 51 | }; 52 | 53 | this.setProjection = function(p) { 54 | projection = p; 55 | this.render(); 56 | }; 57 | 58 | this.getCentralLongitude = function() { 59 | return lon0; 60 | }; 61 | 62 | this.setCentralLongitude = function(centralLongitude) { 63 | if (isNaN(centralLongitude)) { 64 | return; 65 | } 66 | lon0 = centralLongitude; 67 | if (lon0 > Math.PI) { 68 | lon0 -= 2 * Math.PI; 69 | } else if (lon0 < -Math.PI) { 70 | lon0 += 2 * Math.PI; 71 | } 72 | this.render(); 73 | }; 74 | 75 | this.setDraw = function(d) { 76 | draw = d; 77 | this.render(); 78 | }; 79 | 80 | this.isDrawn = function() { 81 | return draw; 82 | }; 83 | 84 | this.clear = function() { 85 | var ctx = canvas.getContext('2d'); 86 | ctx.clearRect(0, 0, canvas.width, canvas.height); 87 | }; 88 | 89 | this.getScale = function() { 90 | return scale; 91 | }; 92 | 93 | this.setScale = function(s) { 94 | scale = s; 95 | this.render(); 96 | }; 97 | 98 | this.getMaximumHorizontalScale = function() { 99 | var graticuleWidth, xy = []; 100 | if ( typeof projection.getGraticuleWidth === 'function') { 101 | graticuleWidth = projection.getGraticuleWidth(); 102 | } else { 103 | // lon = 180, lat = 0 104 | projection.forward(Math.PI, 0, xy); 105 | graticuleWidth = xy[0] * 2; 106 | // lon = 180, lat = 90 107 | projection.forward(Math.PI, Math.PI / 2, xy); 108 | graticuleWidth = Math.max(graticuleWidth, xy[0]); 109 | } 110 | return canvas.width / graticuleWidth; 111 | }; 112 | 113 | this.getMaximumVerticalScale = function() { 114 | var graticuleHeight, xy = []; 115 | if ( typeof projection.getGraticuleHeight === 'function') { 116 | graticuleHeight = projection.getGraticuleHeight(); 117 | } else { 118 | // lon = 0, lat = 90 119 | projection.forward(0, Math.PI / 2, xy); 120 | graticuleHeight = xy[1] * 2; 121 | // lon = 180, lat = 90 122 | projection.forward(Math.PI, Math.PI / 2, xy); 123 | graticuleHeight = Math.max(graticuleHeight, xy[1]); 124 | 125 | } 126 | return canvas.height / graticuleHeight; 127 | }; 128 | 129 | // FIXME this may return the length of the equator and not the actual graticule width 130 | this.getGraticuleWidth = function() { 131 | var graticuleWidth, xy = []; 132 | if ( typeof projection.getGraticuleWidth === 'function') { 133 | graticuleWidth = projection.getGraticuleWidth(); 134 | } else { 135 | // lon = 180, lat = 0 136 | projection.forward(Math.PI, 0, xy); 137 | graticuleWidth = xy[0] * 2; 138 | // lon = 180, lat = 90 139 | projection.forward(Math.PI, Math.PI / 2, xy); 140 | graticuleWidth = Math.max(graticuleWidth, xy[0]); 141 | } 142 | return graticuleWidth; 143 | }; 144 | 145 | // FIXME this may return the length of the central meridian and not the actual graticule height 146 | this.getGraticuleHeight = function() { 147 | var graticuleHeight, xy = []; 148 | if ( typeof projection.getGraticuleHeight === 'function') { 149 | graticuleHeight = projection.getGraticuleHeight(); 150 | } else { 151 | // lon = 0, lat = 90 152 | projection.forward(0, Math.PI / 2, xy); 153 | graticuleHeight = xy[1] * 2; 154 | // lon = 180, lat = 90 155 | projection.forward(Math.PI, Math.PI / 2, xy); 156 | graticuleHeight = Math.max(graticuleHeight, xy[1]); 157 | 158 | } 159 | return graticuleHeight; 160 | }; 161 | 162 | this.scaleMapToCanvas = function() { 163 | var hScale, vScale; 164 | hScale = this.getMaximumHorizontalScale(); 165 | vScale = this.getMaximumVerticalScale(); 166 | this.setScale(Math.min(hScale, vScale)); 167 | }; 168 | 169 | this.render = function() { 170 | var i; 171 | this.clear(); 172 | if (draw) { 173 | for ( i = 0; i < layers.length; i += 1) { 174 | layers[i].render(projection, lon0, scale, canvas); 175 | } 176 | } 177 | }; 178 | 179 | this.resize = function(width, height) { 180 | // use size of DOM element for canvas backing store, and 181 | // increase size of canvas backing store on high-resolution displays, such as Apple's Retina 182 | var scale = backingScale(canvas.getContext('2d')); 183 | canvas.setAttribute('width', width * scale); 184 | canvas.setAttribute('height', height * scale); 185 | this.scaleMapToCanvas(); 186 | this.render(); 187 | }; 188 | } 189 | 190 | function createMap(layers, projection, canvasElement, width, height) {"use strict"; 191 | var map = new Map(layers, canvasElement); 192 | map.setProjection(projection); 193 | map.resize(width, height); 194 | map.load(); 195 | return map; 196 | } 197 | -------------------------------------------------------------------------------- /CanvasMap/src/binarywrapper.js: -------------------------------------------------------------------------------- 1 | // 2 | // stateful helper for binaryajax.js's BinaryFile class 3 | // 4 | // modelled on Flash's ByteArray, mostly, although some names 5 | // (int/short/long) differ in definition 6 | // 7 | 8 | function BinaryFileWrapper(binFile) { 9 | 10 | this.position = 0; 11 | this.bigEndian = true; 12 | 13 | this.getByte = function() { 14 | var Byte = binFile.getByteAt(this.position); 15 | this.position++; 16 | return Byte; 17 | } 18 | 19 | this.getLength = function() { 20 | return binFile.getLength(); 21 | } 22 | 23 | this.getSByte = function() { 24 | var sbyte = binFile.getSByteAt(this.position); 25 | this.position++; 26 | return sbyte; 27 | } 28 | 29 | this.getShort = function() { 30 | var Short = binFile.getShortAt(this.position, this.bigEndian); 31 | this.position += 2; 32 | return Short; 33 | } 34 | 35 | this.getSShort = function() { 36 | var sshort = binFile.getSShortAt(this.position, this.bigEndian); 37 | this.position += 2; 38 | return sshort; 39 | } 40 | 41 | this.getLong = function() { 42 | var l = binFile.getLongAt(this.position, this.bigEndian); 43 | this.position += 4; 44 | return l; 45 | } 46 | 47 | this.getSLong = function() { 48 | var l = binFile.getSLongAt(this.position, this.bigEndian); 49 | this.position += 4; 50 | return l; 51 | } 52 | 53 | this.getString = function(iLength) { 54 | var s = binFile.getStringAt(this.position, iLength); 55 | this.position += iLength; 56 | return s; 57 | } 58 | 59 | this.getDoubleAt = function(iOffset, bBigEndian) { 60 | // hugs stackoverflow 61 | // http://stackoverflow.com/questions/1597709/convert-a-string-with-a-hex-representation-of-an-ieee-754-double-into-javascript 62 | // TODO: check the endianness for something other than shapefiles 63 | // TODO: what about NaNs and Infinity? 64 | var a = binFile.getLongAt(iOffset + (bBigEndian ? 0 : 4), bBigEndian); 65 | var b = binFile.getLongAt(iOffset + (bBigEndian ? 4 : 0), bBigEndian); 66 | var s = a >> 31 ? -1 : 1; 67 | var e = (a >> 52 - 32 & 0x7ff) - 1023; 68 | return s * (a & 0xfffff | 0x100000) * 1.0 / Math.pow(2,52-32) * Math.pow(2, e) + b * 1.0 / Math.pow(2, 52) * Math.pow(2, e); 69 | } 70 | 71 | this.getDouble = function() { 72 | var d = this.getDoubleAt(this.position, this.bigEndian); 73 | this.position += 8; 74 | return d; 75 | } 76 | 77 | this.getChar = function() { 78 | var c = binFile.getCharAt(this.position); 79 | this.position++; 80 | return c; 81 | } 82 | } -------------------------------------------------------------------------------- /CanvasMap/src/dbf.js: -------------------------------------------------------------------------------- 1 | // ported from http://code.google.com/p/vanrijkom-flashlibs/ under LGPL v2.1 2 | 3 | function DbfFile(binFile) { 4 | 5 | this.src = new BinaryFileWrapper(binFile); 6 | 7 | var t1 = new Date().getTime(); 8 | 9 | this.header = new DbfHeader(this.src); 10 | 11 | var t2 = new Date().getTime(); 12 | //if (window.console && window.console.log) console.log('parsed dbf header in ' + (t2-t1) + ' ms'); 13 | 14 | t1 = new Date().getTime(); 15 | 16 | // TODO: could maybe be smarter about this and only parse these on demand 17 | this.records = []; 18 | for (var i = 0; i < this.header.recordCount; i++) { 19 | var record = this.getRecord(i); 20 | this.records.push(record); 21 | } 22 | 23 | t2 = new Date().getTime(); 24 | //if (window.console && window.console.log) console.log('parsed dbf records in ' + (t2-t1) + ' ms'); 25 | 26 | } 27 | DbfFile.prototype.getRecord = function(index) { 28 | 29 | if (index > this.header.recordCount) { 30 | throw(new DbfError("",DbfError.ERROR_OUTOFBOUNDS)); 31 | } 32 | 33 | this.src.position = this.header.recordsOffset + index * this.header.recordSize; 34 | this.src.bigEndian = false; 35 | 36 | return new DbfRecord(this.src, this.header); 37 | } 38 | 39 | 40 | function DbfHeader(src) { 41 | 42 | // endian: 43 | src.bigEndian = false; 44 | 45 | this.version = src.getSByte(); 46 | this.updateYear = 1900+src.getByte(); 47 | this.updateMonth = src.getByte(); 48 | this.updateDay = src.getByte(); 49 | this.recordCount = src.getLong(); 50 | this.headerSize = src.getShort(); 51 | this.recordSize = src.getShort(); 52 | 53 | //skip 2: 54 | src.position += 2; 55 | 56 | this.incompleteTransaction = src.getByte(); 57 | this.encrypted = src.getByte(); 58 | 59 | // skip 12: 60 | src.position += 12; 61 | 62 | this.mdx = src.getByte(); 63 | this.language = src.getByte(); 64 | 65 | // skip 2; 66 | src.position += 2; 67 | 68 | // iterate field descriptors: 69 | this.fields = []; 70 | while (src.getSByte() != 0x0D){ 71 | src.position -= 1; 72 | this.fields.push(new DbfField(src)); 73 | } 74 | 75 | this.recordsOffset = this.headerSize+1; 76 | 77 | } 78 | 79 | function DbfField(src) { 80 | 81 | this.name = this.readZeroTermANSIString(src); 82 | 83 | // fixed length: 10, so: 84 | src.position += (10-this.name.length); 85 | 86 | this.type = src.getByte(); 87 | this.address = src.getLong(); 88 | this.length = src.getByte(); 89 | this.decimals = src.getByte(); 90 | 91 | // skip 2: 92 | src.position += 2; 93 | 94 | this.id = src.getByte(); 95 | 96 | // skip 2: 97 | src.position += 2; 98 | 99 | this.setFlag = src.getByte(); 100 | 101 | // skip 7: 102 | src.position += 7; 103 | 104 | this.indexFlag = src.getByte(); 105 | } 106 | DbfField.prototype.readZeroTermANSIString = function(src) { 107 | var r = []; 108 | var b; 109 | while (b = src.getByte()) { 110 | r[r.length] = String.fromCharCode(b); 111 | } 112 | return r.join(''); 113 | } 114 | 115 | function DbfRecord(src, header) { 116 | this.offset = src.position; 117 | this.values = {} 118 | for (var i = 0; i < header.fields.length; i++) { 119 | var field = header.fields[i]; 120 | this.values[field.name] = src.getString(field.length); 121 | } 122 | } -------------------------------------------------------------------------------- /CanvasMap/src/lib/binaryajax.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/CanvasMap/src/lib/binaryajax.js -------------------------------------------------------------------------------- /CanvasMap/src/projections/AlbersConic.js: -------------------------------------------------------------------------------- 1 | function AlbersConic() {"use strict"; 2 | 3 | var c, rho0, n, n2, EPS10 = 1.0e-10; 4 | 5 | this.toString = function() { 6 | return 'Albers Conic'; 7 | }; 8 | 9 | this.initialize = function(phi0, phi1, phi2) { 10 | var cosPhi1, sinPhi1, secant; 11 | 12 | if (Math.abs(phi1 + phi2) < EPS10) { 13 | n = NaN; 14 | throw new Error("Standard latitudes of Albers conic too close to equator"); 15 | } 16 | 17 | cosPhi1 = Math.cos(phi1); 18 | sinPhi1 = Math.sin(phi1); 19 | secant = Math.abs(phi1 - phi2) >= EPS10; 20 | if (secant) { 21 | n = 0.5 * (sinPhi1 + Math.sin(phi2)); 22 | } else { 23 | n = sinPhi1; 24 | } 25 | n2 = 2 * n; 26 | c = cosPhi1 * cosPhi1 + n2 * sinPhi1; 27 | rho0 = Math.sqrt(c - n2 * Math.sin(phi0)) / n; 28 | }; 29 | 30 | this.forward = function(lon, lat, xy) { 31 | var rho, n_x_lon; 32 | rho = c - n2 * Math.sin(lat); 33 | if (rho < 0) { 34 | xy[0] = NaN; 35 | xy[1] = NaN; 36 | } 37 | rho = Math.sqrt(rho) / n; 38 | n_x_lon = n * lon; 39 | xy[0] = rho * Math.sin(n_x_lon); 40 | xy[1] = rho0 - rho * Math.cos(n_x_lon); 41 | }; 42 | 43 | this.initialize(45/180*Math.PI, 45/180*Math.PI, 45/180*Math.PI); 44 | } 45 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Arden-Close.js: -------------------------------------------------------------------------------- 1 | function ArdenClose() {"use strict"; 2 | 3 | var MAX_LAT = 88 / 180 * Math.PI; 4 | 5 | this.toString = function() { 6 | return 'Arden-Close'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | var y1, y2; 11 | 12 | if (lat > MAX_LAT) { 13 | lat = MAX_LAT; 14 | } else if (lat < -MAX_LAT) { 15 | lat = -MAX_LAT; 16 | } 17 | 18 | y1 = Math.log(Math.tan(Math.PI / 4 + 0.5 * lat)); 19 | y2 = Math.sin(lat); 20 | 21 | xy[0] = lon; 22 | xy[1] = (y1 + y2) / 2; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/AspectAdaptiveCylindrical.js: -------------------------------------------------------------------------------- 1 | function AspectAdaptiveCylindrical() {"use strict"; 2 | var A = [9.684, -33.44, 43.13, -19.77, -0.569, -0.875, 7.002, -5.948, -0.509, 3.333, -6.705, 4.148]; 3 | var B = [0.0186, -0.0215, -1.179, 1.837]; 4 | 5 | var MIN_ASPECT = 0.3, MAX_ASPECT = 1, PI_HALF = Math.PI / 2, EXTRA_ASPECT_LIMIT = 0.7, EXTRA_LAT_LIMIT = 45 / 180 * Math.PI; 6 | var aspectRatio, k11, k12, k13, k21, k22; 7 | 8 | this.toString = function() { 9 | return 'Aspect Adaptive Cylindrical'; 10 | }; 11 | 12 | this.getMinAspectRatio = function() { 13 | return MIN_ASPECT; 14 | }; 15 | 16 | this.getMaxAspectRatio = function() { 17 | return MAX_ASPECT; 18 | }; 19 | 20 | this.getAspectRatio = function(aspect) { 21 | return aspectRatio; 22 | }; 23 | 24 | this.setAspectRatio = function(aspect) { 25 | aspectRatio = Math.max(Math.min(aspect, MAX_ASPECT), MIN_ASPECT); 26 | 27 | var k11x, k12x, k13x, n; 28 | 29 | if (aspectRatio > EXTRA_ASPECT_LIMIT) { 30 | k11x = A[0] + EXTRA_ASPECT_LIMIT * (A[1] + EXTRA_ASPECT_LIMIT * (A[2] + EXTRA_ASPECT_LIMIT * A[3])); 31 | k12x = A[4] + EXTRA_ASPECT_LIMIT * (A[5] + EXTRA_ASPECT_LIMIT * (A[6] + EXTRA_ASPECT_LIMIT * A[7])); 32 | k13x = A[8] + EXTRA_ASPECT_LIMIT * (A[9] + EXTRA_ASPECT_LIMIT * (A[10] + EXTRA_ASPECT_LIMIT * A[11])); 33 | 34 | n = PI_HALF * (k11x + PI_HALF * PI_HALF * (k12x + PI_HALF * PI_HALF * k13x)); 35 | 36 | k11 = k11x * EXTRA_ASPECT_LIMIT * Math.PI / n; 37 | k12 = k12x * EXTRA_ASPECT_LIMIT * Math.PI / n; 38 | k13 = k13x * EXTRA_ASPECT_LIMIT * Math.PI / n; 39 | 40 | var k21x = B[0] + aspectRatio * B[1]; 41 | var k22x = B[2] + aspectRatio * B[3]; 42 | 43 | var poleDiff = PI_HALF - EXTRA_ASPECT_LIMIT; 44 | var n_2 = poleDiff * (k21x + poleDiff * poleDiff * k22x ); 45 | 46 | var aspectDiff = aspectRatio - EXTRA_ASPECT_LIMIT; 47 | k21 = k21x * Math.PI * aspectDiff / n_2; 48 | k22 = k22x * Math.PI * aspectDiff / n_2; 49 | 50 | } else { 51 | k11x = A[0] + aspectRatio * (A[1] + aspectRatio * (A[2] + aspectRatio * A[3])); 52 | k12x = A[4] + aspectRatio * (A[5] + aspectRatio * (A[6] + aspectRatio * A[7])); 53 | k13x = A[8] + aspectRatio * (A[9] + aspectRatio * (A[10] + aspectRatio * A[11])); 54 | 55 | n = PI_HALF * (k11x + PI_HALF * PI_HALF * (k12x + PI_HALF * PI_HALF * k13x)); 56 | 57 | k11 = k11x * aspectRatio * Math.PI / n; 58 | k12 = k12x * aspectRatio * Math.PI / n; 59 | k13 = k13x * aspectRatio * Math.PI / n; 60 | 61 | k21 = k22 = 0.0; 62 | } 63 | 64 | }; 65 | 66 | this.forward = function(lon, lat, xy) { 67 | var lat_diff, lat2 = lat * lat; 68 | xy[0] = lon; 69 | xy[1] = lat * (k11 + lat2 * (k12 + lat2 * k13)); 70 | 71 | if ((aspectRatio > EXTRA_ASPECT_LIMIT) && (lat > EXTRA_LAT_LIMIT)) { 72 | lat_diff = lat - EXTRA_LAT_LIMIT; 73 | xy[1] += lat_diff * (k21 + lat_diff * lat_diff * k22); 74 | } 75 | if ((aspectRatio > EXTRA_ASPECT_LIMIT) && (lat < -EXTRA_LAT_LIMIT)) { 76 | lat_diff = lat + EXTRA_LAT_LIMIT; 77 | xy[1] += lat_diff * (k21 + lat_diff * lat_diff * k22); 78 | } 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Bonne.js: -------------------------------------------------------------------------------- 1 | function Bonne() {"use strict"; 2 | var phi1 = 85 / 180 * Math.PI, cotphi1 = 1 / Math.tan(phi1); 3 | 4 | this.toString = function() { 5 | return 'Bonne (Equal Area)'; 6 | }; 7 | 8 | this.getGraticuleHeight = function() { 9 | return 6; 10 | }; 11 | 12 | this.getStandardParallel = function() { 13 | return phi1; 14 | }; 15 | 16 | this.setStandardParallel = function(stdParallel) { 17 | phi1 = stdParallel; 18 | cotphi1 = 1 / Math.tan(phi1); 19 | }; 20 | 21 | this.forward = function(lon, lat, xy) { 22 | var r = cotphi1 + phi1 - lat; 23 | var E = lon * Math.cos(lat) / r; 24 | xy[0] = r * Math.sin(E); 25 | xy[1] = cotphi1 - r * Math.cos(E); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Braun2.js: -------------------------------------------------------------------------------- 1 | function Braun2() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Braun2'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | xy[0] = lon; 9 | xy[1] = 7 / 5 * Math.sin(lat) / (2 / 5 + Math.cos(lat)); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Canters1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Canters, F. (2002) Small-scale Map projection Design. p. 218-219. 3 | * Modified Sinusoidal, equal-area. 4 | */ 5 | function Canters1() {"use strict"; 6 | 7 | var C1 = 1.1966, C3 = -0.1290, C3x3 = 3 * C3, C5 = -0.0076, C5x5 = 5 * C5; 8 | 9 | this.toString = function() { 10 | return 'Canters Modified Sinusoidal I'; 11 | }; 12 | 13 | this.forward = function(lon, lat, xy) { 14 | var y2 = lat * lat, 15 | y4 = y2 * y2; 16 | xy[0] = lon * Math.cos(lat) / (C1 + C3x3 * y2 + C5x5 * y4); 17 | xy[1] = lat * (C1 + C3 * y2 + C5 * y4); 18 | }; 19 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Canters2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Canters, F. (2002) Small-scale Map projection Design. p. 218-220. 3 | * Modified Sinusoidal, equal-area. 4 | */ 5 | function Canters2() {"use strict"; 6 | 7 | var C1 = 1.1481, C3 = -0.0753, C3x3 = 3 * C3, C5 = -0.0150, C5x5 = 5 * C5; 8 | 9 | this.toString = function() { 10 | return 'Canters Modified Sinusoidal II'; 11 | }; 12 | 13 | this.forward = function(lon, lat, xy) { 14 | var y2 = lat * lat, 15 | y4 = y2 * y2; 16 | xy[0] = lon * Math.cos(lat) / (C1 + C3x3 * y2 + C5x5 * y4); 17 | xy[1] = lat * (C1 + C3 * y2 + C5 * y4); 18 | }; 19 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/CentralCylindrical.js: -------------------------------------------------------------------------------- 1 | function CentralCylindrical() {"use strict"; 2 | 3 | var MAX_LAT = 80 / 180 * Math.PI; 4 | 5 | this.toString = function() { 6 | return 'Central Cylindrical'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | if (lat > MAX_LAT) { 11 | lat = MAX_LAT; 12 | } else if (lat < -MAX_LAT) { 13 | lat = -MAX_LAT; 14 | } 15 | xy[0] = lon; 16 | xy[1] = Math.tan(lat); 17 | }; 18 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/CylindricalStereographic.js: -------------------------------------------------------------------------------- 1 | function CylindricalStereographic() {"use strict"; 2 | var cosPhi0 = Math.cos(30 / 180 * Math.PI); 3 | 4 | this.toString = function() { 5 | return 'Cylindrical Stereographic'; 6 | }; 7 | 8 | this.getStandardParallel = function() { 9 | return Math.acos(cosPhi0); 10 | }; 11 | 12 | this.setStandardParallel = function(phi0) { 13 | cosPhi0 = Math.cos(phi0); 14 | }; 15 | 16 | this.forward = function(lon, lat, xy) { 17 | xy[0] = lon * cosPhi0; 18 | xy[1] = (1 + cosPhi0) * Math.tan(lat / 2); 19 | }; 20 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Eckert4.js: -------------------------------------------------------------------------------- 1 | function Eckert4() {"use strict"; 2 | 3 | var C_x = 0.42223820031577120149, 4 | C_y = 1.32650042817700232218, 5 | C_p = 3.57079632679489661922, 6 | EPS = 1.0e-7, 7 | NITER = 6, 8 | ONE_TOL = 1.00000000000001, 9 | HALFPI = Math.PI / 2; 10 | 11 | this.toString = function() { 12 | return 'Eckert IV'; 13 | }; 14 | 15 | this.forward = function(lon, lat, xy) { 16 | 17 | var p, V, s, c, i; 18 | 19 | p = C_p * Math.sin(lat); 20 | V = lat * lat; 21 | lat *= 0.895168 + V * (0.0218849 + V * 0.00826809); 22 | for ( i = NITER; i > 0; --i) { 23 | c = Math.cos(lat); 24 | s = Math.sin(lat); 25 | lat -= V = (lat + s * (c + 2) - p) / (1 + c * (c + 2) - s * s); 26 | if (Math.abs(V) < EPS) { 27 | xy[0] = C_x * lon * (1 + Math.cos(lat)); 28 | xy[1] = C_y * Math.sin(lat); 29 | return; 30 | } 31 | } 32 | xy[0] = C_x * lon; 33 | xy[1] = lat < 0 ? -C_y : C_y; 34 | }; 35 | 36 | this.inverse = function (x, y, lonlat) { 37 | var sinTheta = y / 1.3265004; 38 | var theta = Math.asin(sinTheta); 39 | lonlat[0] = x / (0.4222382 * (1 + Math.cos(theta))); 40 | lonlat[1] = Math.asin((theta + sinTheta * Math.cos(theta) + 2 * sinTheta) / (2 + Math.PI / 2)); 41 | }; 42 | 43 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Eckert6.js: -------------------------------------------------------------------------------- 1 | function Eckert6() {"use strict"; 2 | 3 | var n = 2.570796326794896619231321691, 4 | C_y = Math.sqrt((2) / n), 5 | C_x = C_y / 2, 6 | MAX_ITER = 8, 7 | LOOP_TOL = 1e-7; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | var i, v, k = n * Math.sin(lat); 11 | for (i = MAX_ITER; i > 0;) { 12 | lat -= v = (lat + Math.sin(lat) - k) / (1 + Math.cos(lat)); 13 | if (Math.abs(v) < LOOP_TOL) { 14 | break; 15 | } 16 | --i; 17 | } 18 | 19 | xy[0] = C_x * lon * (1 + Math.cos(lat)); 20 | xy[1] = C_y * lat; 21 | }; 22 | 23 | this.inverse = function (x, y, lonlat) { 24 | y /= C_y; 25 | lonlat[1] = Math.asin((y + Math.sin(y)) / n); 26 | lonlat[0] = x / (C_x * (1 + Math.cos(y))); 27 | }; 28 | 29 | this.toString = function() { 30 | return 'Eckert VI'; 31 | }; 32 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/EqualAreaCylindrical.js: -------------------------------------------------------------------------------- 1 | function EqualAreaCylindrical() {"use strict"; 2 | var cosPhi0 = Math.cos(30 / 180 * Math.PI); 3 | 4 | this.toString = function() { 5 | return 'Cylindrical Equal-Area'; 6 | }; 7 | 8 | this.getStandardParallel = function() { 9 | return Math.acos(cosPhi0); 10 | }; 11 | 12 | this.setStandardParallel = function(phi0) { 13 | cosPhi0 = Math.cos(phi0); 14 | }; 15 | 16 | this.forward = function(lon, lat, xy) { 17 | xy[0] = lon * cosPhi0; 18 | xy[1] = Math.sin(lat) / cosPhi0; 19 | }; 20 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Equirectangular.js: -------------------------------------------------------------------------------- 1 | function Equirectangular() {"use strict"; 2 | var cosPhi0 = Math.cos(30 / 180 * Math.PI); 3 | 4 | this.toString = function() { 5 | return 'Equirectangular'; 6 | }; 7 | 8 | this.getStandardParallel = function() { 9 | return Math.acos(cosPhi0); 10 | }; 11 | 12 | this.setStandardParallel = function(phi0) { 13 | cosPhi0 = Math.cos(phi0); 14 | }; 15 | 16 | this.forward = function(lon, lat, xy) { 17 | xy[0] = lon * cosPhi0; 18 | xy[1] = lat; 19 | }; 20 | 21 | this.inverse = function (x, y, lonlat) { 22 | lonlat[0] = x / cosPhi0; 23 | lonlat[1] = y; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Hammer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom shapes between a Lambert azimuthal (w = 1) and a Quartic Authalic (w = 0) can be defined with 3 | * the optional argument w. If not provided, the default 0.5 for the Hammer projection is used. 4 | */ 5 | function Hammer(w) { 6 | 7 | "use strict"; 8 | 9 | var w, W_MAX = 0.999999, EPS10 = 1.e-10; 10 | 11 | this.toString = function() { 12 | switch (w) { 13 | case 0: 14 | return 'Quartic Authalic'; 15 | case 0.5: 16 | return 'Hammer'; 17 | default: 18 | return 'Hammer Customized'; 19 | } 20 | }; 21 | 22 | this.forward = function(lon, lat, xy) { 23 | var cosLat = Math.cos(lat), d; 24 | lon *= w; 25 | d = Math.sqrt(2 / (1 + cosLat * Math.cos(lon))); 26 | xy[0] = d * cosLat * Math.sin(lon) / w; 27 | xy[1] = d * Math.sin(lat); 28 | }; 29 | 30 | this.inverse = function (x, y, lonlat) { 31 | var EPS = 1.0e-10; 32 | var wx = w * x; 33 | var z = Math.sqrt(1 - 0.25 * (wx * wx + y * y)); 34 | var zz2_1 = 2 * z * z - 1; 35 | if(Math.abs(zz2_1) < EPS) { 36 | lonlat[0] = NaN; 37 | lonlat[1] = NaN; 38 | } else { 39 | lonlat[0] = Math.atan2(wx * z, zz2_1) / w; 40 | lonlat[1] = Math.asin(z * y); 41 | } 42 | }; 43 | 44 | this.setW = function(weight) { 45 | w = weight; 46 | if(w >= W_MAX) { 47 | w = W_MAX; 48 | } else if(w < 0) { 49 | w = 0; 50 | } 51 | if (w === 0) { 52 | this.forward = this.quarticAuthalicForward; 53 | this.inverse = this.quarticAuthalicInverse; 54 | } else { 55 | //it's already set 56 | //this.forward = hammerForward; 57 | //this.inverse = hammerInverse; 58 | } 59 | }; 60 | this.setW(arguments.length === 0 ? 0.5 : w); 61 | 62 | this.getW = function() { 63 | return w; 64 | }; 65 | 66 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Hufnagel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Hufnagel projection family was introduced by Herbert Hufnagel in 3 | * "Hufnagel, H. 1989. Ein System unecht-zylindrischer Kartennetze für 4 | * Erdkarten. Kartographische Nachrichten, 39(3), 89–96." All projections are 5 | * equal-area. Implementation by Bernhard Jenny, Oregon State University, Bojan 6 | * Savric, Oregon State University, with substantial contributions by Daniel 7 | * "daan" Strebe, Mapthematics. November 2014 to October 2015. 8 | * 9 | * @author Bojan Savric 10 | * @author Bernhard Jenny 11 | */ 12 | 13 | function Hufnagel() { 14 | "use strict"; 15 | 16 | // a, b, psiMax and aspectRatio parameterize the Hufnagel transformation. 17 | var a = 0, 18 | b = 0, 19 | psiMax = Math.PI / 2, 20 | // ratio between equator and central meridian lengths 21 | aspectRatio = 2, 22 | // tolerance for iterative computations 23 | EPS = 1.0e-12, 24 | // maximum number of computations 25 | MAX_ITER = 100, 26 | // size of lookup table 27 | LUT_SIZE = 101, 28 | // lookup tables, yLUT, would be needed for inverse projection 29 | latLUT, 30 | psiLUT, 31 | // parameters pre-computed in init() from a, b, psiMax and aspectRatio 32 | ksq, 33 | k, 34 | c, 35 | // true if the graticule is folding over itself. Only an approximation, not all cases with folding graticules are captured. 36 | graticuleFolding = false; 37 | 38 | function approximatePsiFromLookupTable(lat) { 39 | var w, 40 | psi, 41 | lat_abs = Math.abs(lat), 42 | imid, 43 | imin = 0, 44 | imax = 45 | LUT_SIZE; 46 | 47 | while (true) { 48 | imid = Math.floor((imin + imax) / 2); 49 | if (imid === imin) { 50 | // This also handles abs(phi) == latitudeTable[0] because mid must == min. 51 | break; 52 | } else if (lat_abs > latLUT[imid]) { 53 | imin = imid; 54 | } else { 55 | // abs(phi) < latitudeTable[mid], or abs(phi) == latitudeTable[mid] and mid ≠ 0 56 | imax = imid; 57 | } 58 | } 59 | 60 | w = (lat_abs - latLUT[imin]) / (latLUT[imin + 1] - latLUT[imin]); 61 | psi = w * (psiLUT[imin + 1] - psiLUT[imin]) + psiLUT[imin]; 62 | 63 | return lat < 0 ? -psi : psi; 64 | } 65 | 66 | function initializeLookUpTables() { 67 | var i, 68 | psi, 69 | sin2Psi, 70 | sin4Psi, 71 | sin6Psi, 72 | sinPhi, 73 | phi, 74 | r, 75 | y; 76 | latLUT = []; 77 | psiLUT = []; 78 | graticuleFolding = false; 79 | for ( i = 0; i < LUT_SIZE; i = i + 1) { 80 | // psi is linearly proportional to i 81 | psi = psiMax * i / (LUT_SIZE - 1); 82 | 83 | // phi computed from psi: for equation see Hufnagel 1989 84 | if (i === 0) { 85 | phi = 0; 86 | } else if (i === LUT_SIZE - 1) { 87 | phi = Math.PI / 2; 88 | } else { 89 | sin2Psi = Math.sin(2 * psi); 90 | sin4Psi = Math.sin(4 * psi); 91 | sin6Psi = Math.sin(6 * psi); 92 | sinPhi = 0.25 / Math.PI * k * k * (2 * psi + (1 + a - 0.5 * b) * sin2Psi + 0.5 * (a + b) * sin4Psi + 0.5 * b * sin6Psi); 93 | if (Math.abs(sinPhi) > 1) { 94 | phi = sinPhi > 0 ? Math.PI / 2 : -Math.PI / 2; 95 | } else { 96 | phi = Math.asin(sinPhi); 97 | } 98 | } 99 | 100 | // store values in lookup tables 101 | latLUT.push(phi); 102 | psiLUT.push(psi); 103 | } 104 | } 105 | 106 | this.forward = function(lon, lat, xy) { 107 | var r, 108 | deltaPsi, 109 | deltaPsiNumerator, 110 | deltaPsiDenominator, 111 | psi = 0, 112 | i = 0, 113 | PI_x_sinLat = Math.PI * Math.sin(lat), 114 | psi_x_2; 115 | 116 | if (psiMax === 0) { 117 | // cylindrical equal-area projection 118 | xy[0] = lon * k; 119 | xy[1] = Math.sin(lat) / k; 120 | return; 121 | } 122 | // approximate psi from lookup table 123 | psi = approximatePsiFromLookupTable(lat); 124 | 125 | // iteratively improve psi 126 | while (true) { 127 | psi_x_2 = psi * 2; 128 | deltaPsiNumerator = (ksq / 4) * (psi_x_2 + (1 + a - b / 2) * Math.sin(psi_x_2) + ((a + b) / 2) * Math.sin(2 * psi_x_2) + (b / 2) * Math.sin(3 * psi_x_2)) - PI_x_sinLat; 129 | if (Math.abs(deltaPsiNumerator) < EPS) { 130 | break; 131 | } 132 | deltaPsiDenominator = (ksq / 2) * (1 + (1 + a - (b / 2)) * Math.cos(psi_x_2) + (a + b) * Math.cos(2 * psi_x_2) + (3 * b / 2) * Math.cos(3 * psi_x_2)); 133 | deltaPsi = deltaPsiNumerator / deltaPsiDenominator; 134 | 135 | i = i + 1; 136 | if (!isFinite(deltaPsi) || i > MAX_ITER) { 137 | xy[0] = NaN; 138 | xy[1] = NaN; 139 | return; 140 | } 141 | psi = psi - deltaPsi; 142 | } 143 | 144 | // calculate x and y 145 | r = Math.sqrt(1 + a * Math.cos(2 * psi) + b * Math.cos(4 * psi)); 146 | xy[0] = k * r * c * lon / Math.PI * Math.cos(psi); 147 | xy[1] = k * r / c * Math.sin(psi); 148 | }; 149 | 150 | this.init = function() { 151 | if (psiMax === 0) { 152 | k = Math.sqrt(aspectRatio / Math.PI); 153 | } else { 154 | var xy = [], 155 | width; 156 | ksq = (4 * Math.PI) / (2 * psiMax + (1 + a - b / 2) * Math.sin(2 * psiMax) + ((a + b) / 2) * Math.sin(4 * psiMax) + (b / 2) * Math.sin(6 * psiMax)); 157 | k = Math.sqrt(ksq); 158 | c = Math.sqrt(aspectRatio * Math.sin(psiMax) 159 | * Math.sqrt((1 + a * Math.cos(2 * psiMax) + b * Math.cos(4 * psiMax)) / (1 + a + b))); 160 | initializeLookUpTables(); 161 | } 162 | }; 163 | 164 | this.init(); 165 | 166 | this.setA = function(newA) { 167 | a = newA; 168 | this.init(); 169 | }; 170 | 171 | this.setB = function(newB) { 172 | b = newB; 173 | this.init(); 174 | }; 175 | 176 | /** 177 | * psi max in radians 178 | */ 179 | this.setPsiMax = function(newPsiMax) { 180 | psiMax = newPsiMax; 181 | this.init(); 182 | }; 183 | 184 | /** 185 | * ratio between equator and central meridian, default is 2.0 186 | */ 187 | this.setAspectRatio = function(newAspectRatio) { 188 | aspectRatio = newAspectRatio; 189 | this.init(); 190 | }; 191 | 192 | /** 193 | * Returns true if the graticule is folding, false otherwise. 194 | */ 195 | this.isGraticuleFolding = function() { 196 | return graticuleFolding; 197 | }; 198 | 199 | this.toString = function() { 200 | return 'Hufnagel'; 201 | }; 202 | 203 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Kavrayskiy1.js: -------------------------------------------------------------------------------- 1 | function Kavrayskiy1() {"use strict"; 2 | 3 | var PI_HALF = Math.PI / 2, MERCATOR_MAX_LAT = 70 / 180 * Math.PI, DY, C; 4 | DY = Math.log(Math.tan(0.5 * (PI_HALF + MERCATOR_MAX_LAT))); 5 | C = 1 / Math.cos(MERCATOR_MAX_LAT); 6 | 7 | this.toString = function() { 8 | return 'Kavrayskiy I'; 9 | }; 10 | 11 | this.forward = function(lon, lat, xy) { 12 | 13 | xy[0] = lon; 14 | if (lat > MERCATOR_MAX_LAT) { 15 | xy[1] = (lat - MERCATOR_MAX_LAT) * C + DY; 16 | } else if (lat < -MERCATOR_MAX_LAT) { 17 | xy[1] = (lat + MERCATOR_MAX_LAT) * C - DY; 18 | } else { 19 | xy[1] = Math.log(Math.tan(0.5 * (PI_HALF + lat))); 20 | } 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Kavrayskiy5.js: -------------------------------------------------------------------------------- 1 | function Kavrayskiy5() { 2 | "use strict"; 3 | 4 | var sineSeries = new SineSeries(1.504875, 1.504875 * 0.9); 5 | 6 | this.toString = function () { 7 | return 'Kavrayskiy V'; 8 | }; 9 | 10 | this.forward = sineSeries.forward; 11 | this.inverse = sineSeries.inverse; 12 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/KharchenkoShabanova.js: -------------------------------------------------------------------------------- 1 | function KharchenkoShabanova() {"use strict"; 2 | var K = Math.cos(10 / 180 * Math.PI); 3 | 4 | this.toString = function() { 5 | return 'Kharchenko-Shabanova'; 6 | }; 7 | 8 | this.forward = function(lon, lat, xy) { 9 | var latSqr = lat * lat; 10 | 11 | xy[0] = lon * K; 12 | xy[1] = lat * (0.99 + latSqr * (0.0026263 + latSqr * 0.10734)); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/LambertAzimuthalEqualAreaPolar.js: -------------------------------------------------------------------------------- 1 | function LambertAzimuthalEqualAreaPolar() {"use strict"; 2 | 3 | var FORTPI = Math.PI / 4, southPole = false; 4 | 5 | this.toString = function() { 6 | return 'Lambert Azimuthal Equal Area - Polar'; 7 | }; 8 | 9 | //forwardNorthPole 10 | this.forward = function(lon, lat, xy) { 11 | var y = 2 * Math.sin(FORTPI - lat * 0.5); 12 | xy[0] = y * Math.sin(lon); 13 | xy[1] = y * -Math.cos(lon); 14 | }; 15 | 16 | function forwardSouthPole(lon, lat, xy) { 17 | var y = 2 * Math.cos(FORTPI - lat * 0.5); 18 | xy[0] = y * Math.sin(lon); 19 | xy[1] = y * Math.cos(lon); 20 | } 21 | 22 | 23 | this.getGraticuleWidth = function() { 24 | return 4; 25 | }; 26 | 27 | this.getGraticuleHeight = function() { 28 | return 4; 29 | }; 30 | 31 | this.isPoleInsideGraticule = function() { 32 | return true; 33 | }; 34 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/LambertAzimuthalOblique.js: -------------------------------------------------------------------------------- 1 | function LambertAzimuthalOblique() {"use strict"; 2 | 3 | var EPS10 = 1.e-10, lat0 = 0, cosLat0 = 1, sinLat0 = 0; 4 | 5 | this.toString = function() { 6 | return 'Lambert Azimuthal Oblique'; 7 | }; 8 | 9 | this.initialize = function(lat0) { 10 | cosLat0 = Math.cos(lat0); 11 | sinLat0 = Math.sin(lat0); 12 | }; 13 | 14 | this.forward = function(lon, lat, xy) { 15 | var y, sinLat = Math.sin(lat), cosLat = Math.cos(lat), cosLon = Math.cos(lon), sinLon = Math.sin(lon); 16 | y = 1 + sinLat0 * sinLat + cosLat0 * cosLat * cosLon; 17 | // the projection is indeterminate for lon = PI and lat = -lat0 18 | // this point would have to be plotted as a circle 19 | // The following Math.sqrt will return NaN in this case. 20 | y = Math.sqrt(2 / y); 21 | xy[0] = y * cosLat * sinLon; 22 | xy[1] = y * (cosLat0 * sinLat - sinLat0 * cosLat * cosLon); 23 | }; 24 | 25 | this.initialize(45*Math.PI/180); 26 | } 27 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/LambertEqualAreaCylindrical.js: -------------------------------------------------------------------------------- 1 | function LambertEqualAreaCylindrical() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Lambert Cylindrical Equal-Area'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | xy[0] = lon; 9 | xy[1] = Math.sin(lat); 10 | }; 11 | 12 | this.inverse = function (x, y, lonlat) { 13 | lonlat[0] = x; 14 | lonlat[1] = Math.asin(y); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/McBrydeThomas1.js: -------------------------------------------------------------------------------- 1 | function McBrydeThomas1() { 2 | "use strict"; 3 | 4 | var sineSeries = new SineSeries(1.48875, 1.36509); 5 | 6 | this.toString = function () { 7 | return 'McBryde-Thomas I'; 8 | }; 9 | 10 | this.forward = sineSeries.forward; 11 | this.inverse = sineSeries.inverse; 12 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/McBrydeThomas2.js: -------------------------------------------------------------------------------- 1 | function McBrydeThomas2() { 2 | "use strict"; 3 | 4 | var MAX_ITER = 10, 5 | LOOP_TOL = 1e-7, 6 | C1 = 0.45503, 7 | C2 = 1.36509, 8 | C3 = 1.41546, 9 | C_x = 0.22248, 10 | C_y = 1.44492, 11 | C1_2 = 0.33333333333333333333333333; 12 | 13 | 14 | this.toString = function () { 15 | return 'McBryde-Thomas II'; 16 | }; 17 | 18 | this.forward = function (lon, lat, xy) { 19 | var V, t, i, k = C3 * Math.sin(lat); 20 | for (i = MAX_ITER; i; --i) { 21 | t = lat / C2; 22 | lat -= V = (C1 * Math.sin(t) + Math.sin(lat) - k) / 23 | (C1_2 * Math.cos(t) + Math.cos(lat)); 24 | if (Math.abs(V) < LOOP_TOL) 25 | break; 26 | } 27 | t = lat / C2; 28 | xy[0] = C_x * lon * (1. + 3. * Math.cos(lat) / Math.cos(t)); 29 | xy[1] = C_y * Math.sin(t); 30 | }; 31 | 32 | this.inverse = function (x, y, lonlat) { 33 | var t = Math.asin(y / C_y), 34 | phi = C2 * t; 35 | lonlat[0] = x / (C_x * (1 + 3 * Math.cos(phi) / Math.cos(t))); 36 | lonlat[1] = Math.asin((C1 * Math.sin(t) + Math.sin(phi)) / C3); 37 | }; 38 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Mercator.js: -------------------------------------------------------------------------------- 1 | function Mercator() {"use strict"; 2 | 3 | var PI_HALF = Math.PI / 2, WEB_MERCATOR_MAX_LAT = 1.4844222297453322; 4 | 5 | this.toString = function() { 6 | return 'Mercator'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | xy[0] = lon; 11 | if (lat > WEB_MERCATOR_MAX_LAT) { 12 | xy[1] = Math.PI; 13 | } else if (lat < -WEB_MERCATOR_MAX_LAT) { 14 | xy[1] = -Math.PI; 15 | } else { 16 | xy[1] = Math.log(Math.tan(0.5 * (PI_HALF + lat))); 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Miller.js: -------------------------------------------------------------------------------- 1 | // by O. M. Miller 2 | function Miller() {"use strict"; 3 | 4 | this.toString = function() { 5 | return 'Miller'; 6 | }; 7 | 8 | this.forward = function(lon, lat, xy) { 9 | xy[0] = lon; 10 | xy[1] = Math.log(Math.tan(Math.PI / 4 + lat * 0.4)) * 1.25; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Miller2.js: -------------------------------------------------------------------------------- 1 | // by O. M. Miller 2 | function Miller2() {"use strict"; 3 | 4 | this.toString = function() { 5 | return 'Miller II'; 6 | }; 7 | 8 | this.forward = function(lon, lat, xy) { 9 | xy[0] = lon; 10 | xy[1] = Math.log(Math.tan(Math.PI / 4 + lat / 3)) * 1.5; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/MillerPerspective.js: -------------------------------------------------------------------------------- 1 | function MillerPerspective() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Miller Perspective'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | xy[0] = lon; 9 | xy[1] = (Math.sin(lat / 2) + Math.tan(lat / 2)); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/MillerTransformation.js: -------------------------------------------------------------------------------- 1 | function MillerTransformation() {"use strict"; 2 | 3 | var PI_HALF = Math.PI / 2, EPS = 1e-4, n = 1.5, m = 1.5; 4 | 5 | this.toString = function() { 6 | return 'Miller Transformation'; 7 | }; 8 | 9 | this.getM = function() { 10 | return m; 11 | }; 12 | 13 | this.setM = function(M) { 14 | m = M; 15 | }; 16 | 17 | this.getN = function() { 18 | return n; 19 | }; 20 | 21 | this.setN = function(N) { 22 | n = N; 23 | }; 24 | 25 | this.getAspectRatio = function() { 26 | return n * Math.log(Math.tan(0.5 * PI_HALF * (1 + 1 / n))) / Math.PI; 27 | }; 28 | 29 | this.setAspectRatio = function(aspect) { 30 | if (aspect <= 0.5) { 31 | m = n = 1 / EPS; 32 | } else { 33 | var Cc = 1.05, tol = 1, F, Fder1, Fder2, angle; 34 | while (Math.abs(tol) > EPS) { 35 | angle = 0.5 * PI_HALF * (1 + 1 / Cc); 36 | F = Cc * Math.log(Math.tan(angle)) - aspect * Math.PI; 37 | Fder1 = Math.log(Math.tan(angle)); 38 | Fder2 = Math.tan(angle) * Math.cos(angle) * Math.cos(angle) * Cc; 39 | Cc -= tol = F / (Fder1 - 0.5 * PI_HALF / Fder2); 40 | } 41 | m = n = Cc; 42 | } 43 | }; 44 | 45 | this.forward = function(lon, lat, xy) { 46 | xy[0] = lon; 47 | xy[1] = m * Math.log(Math.tan(0.5 * (PI_HALF + lat / n))); 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Mollweide.js: -------------------------------------------------------------------------------- 1 | function Mollweide() {"use strict"; 2 | 3 | var MAX_ITER = 10, 4 | TOLERANCE = 1.0e-7, 5 | cx, cy, cp; 6 | 7 | this.toString = function() { 8 | return 'Mollweide'; 9 | }; 10 | 11 | // FIXME 12 | (function() { 13 | var p = Math.PI / 2, r, sp, p2 = p + p; 14 | sp = Math.sin(p); 15 | r = Math.sqrt(Math.PI * 2.0 * sp / (p2 + Math.sin(p2))); 16 | cx = 2 * r / Math.PI; 17 | cy = r / sp; 18 | cp = p2 + Math.sin(p2); 19 | })(); 20 | 21 | this.forward = function(lon, lat, xy) { 22 | var k, v, i; 23 | k = cp * Math.sin(lat); 24 | for ( i = MAX_ITER; i !== 0; i--) { 25 | lat -= v = (lat + Math.sin(lat) - k) / (1 + Math.cos(lat)); 26 | if (Math.abs(v) < TOLERANCE) { 27 | break; 28 | } 29 | } 30 | if (i === 0) { 31 | lat = (lat < 0) ? -Math.PI / 2 : Math.PI / 2; 32 | } else { 33 | lat *= 0.5; 34 | } 35 | xy[0] = cx * lon * Math.cos(lat); 36 | xy[1] = cy * Math.sin(lat); 37 | }; 38 | 39 | this.inverse = function (x, y, lonlat) { 40 | var theta, sinTheta, cosTheta; 41 | // 1 / sqrt(2) = 0.70710678118655 42 | sinTheta = y * 0.70710678118655; 43 | theta = Math.asin(sinTheta); 44 | cosTheta = Math.cos(theta); 45 | // Math.PI / (2 * sqrt(2)) = 1.11072073453959 46 | lonlat[0] = x * 1.11072073453959 / cosTheta; 47 | lonlat[1] = Math.asin(2 * (theta + sinTheta * cosTheta) / Math.PI); 48 | }; 49 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/NaturalEarth.js: -------------------------------------------------------------------------------- 1 | function NaturalEarth() {"use strict"; 2 | 3 | var MAX_Y = 0.8707 * 0.52 * Math.PI; 4 | 5 | this.toString = function() { 6 | return 'Natural Earth'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | var lat2 = lat * lat, lat4 = lat2 * lat2; 11 | 12 | xy[0] = lon * (0.8707 - 0.131979 * lat2 + lat4 * (-0.013791 + lat4 * (0.003971 * lat2 - 0.001529 * lat4))); 13 | xy[1] = lat * (1.007226 + lat2 * (0.015085 + lat4 * (-0.044475 + 0.028874 * lat2 - 0.005916 * lat4))); 14 | }; 15 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Pavlov.js: -------------------------------------------------------------------------------- 1 | function Pavlov() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Pavlov'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | var phi3, phi5; 9 | 10 | phi3 = lat * lat * lat; 11 | phi5 = phi3 * lat * lat; 12 | 13 | xy[0] = lon; 14 | xy[1] = (lat - 0.1531 / 3 * phi3 - 0.0267 / 5 * phi5); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/PutninsP4P.js: -------------------------------------------------------------------------------- 1 | function PutninsP4P() {"use strict"; 2 | 3 | var C_x = 0.874038744, 4 | C_y = 3.883251825; 5 | 6 | this.toString = function() { 7 | return 'Putnins P4\''; 8 | }; 9 | 10 | this.forward = function(lon, lat, xy) { 11 | lat = Math.asin(0.883883476 * Math.sin(lat)); 12 | xy[0] = C_x * lon * Math.cos(lat); 13 | xy[0] /= Math.cos(lat *= 0.333333333333333); 14 | xy[1] = C_y * Math.sin(lat); 15 | }; 16 | 17 | this.inverse = function (x, y, lonlat) { 18 | lonlat[1] = Math.asin(y / C_y); 19 | lonlat[0] = x * Math.cos(lonlat[1]) / C_x; 20 | lonlat[1] *= 3.; 21 | lonlat[0] /= Math.cos(lonlat[1]); 22 | lonlat[1] = Math.asin(1.13137085 * Math.sin(lonlat[1])); 23 | }; 24 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Robinson.js: -------------------------------------------------------------------------------- 1 | /** 2 | Approximation by Canters & Decleir 3 | */ 4 | function Robinson() {"use strict"; 5 | this.toString = function() { 6 | return 'Robinson'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | var lat2 = lat * lat; 11 | xy[0] = lon * (0.8507 - lat2 * (0.1450 + lat2 * 0.0104)); 12 | xy[1] = lat * (0.9642 - lat2 * (0.0013 + lat2 * 0.0129)); 13 | }; 14 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/SineSeries.js: -------------------------------------------------------------------------------- 1 | function SineSeries(p, q) { 2 | "use strict"; 3 | 4 | if (arguments.length < 2) { 5 | p = 1.33; 6 | q = 1.135; 7 | } 8 | 9 | var k = q / p; 10 | 11 | this.toString = function () { 12 | return 'Sine Series'; 13 | }; 14 | 15 | this.setP = function (newP) { 16 | p = parseFloat(newP); 17 | k = q / p; 18 | }; 19 | 20 | this.setQ = function (newQ) { 21 | q = parseFloat(newQ); 22 | k = q / p; 23 | }; 24 | 25 | this.forward = function (lon, lat, xy) { 26 | var x = k * lon * Math.cos(lat); 27 | lat /= q; 28 | xy[0] = x / Math.cos(lat); 29 | xy[1] = p * Math.sin(lat); 30 | }; 31 | 32 | this.inverse = function (x, y, lonlat) { 33 | y /= p; 34 | var lat = Math.asin(y), 35 | c = Math.cos(lat); 36 | lonlat[1] = lat *= q; 37 | lonlat[0] = x / (k * Math.cos(lat)) * c; 38 | }; 39 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Sinusoidal.js: -------------------------------------------------------------------------------- 1 | function Sinusoidal() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Sinusoidal (Equal Area)'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | xy[0] = lon * Math.cos(lat); 9 | xy[1] = lat; 10 | }; 11 | 12 | this.inverse = function (x, y, lonlat) { 13 | lonlat[0] = x / Math.cos(y); 14 | lonlat[1] = y; 15 | }; 16 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Strebe1995.js: -------------------------------------------------------------------------------- 1 | function Strebe1995() {"use strict"; 2 | 3 | var kg = 1.35, forward1 = new Eckert4(), inv = new Mollweide(), forward2 = new Hammer(); 4 | 5 | this.toString = function() { 6 | return 'Strebe 1995 (Equal Area)'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | // Eckert IV 11 | forward1.forward(lon, lat, xy); 12 | // Mollweide 13 | inv.inverse(xy[0] * kg, xy[1] / kg, xy); 14 | // Hammer 15 | forward2.forward(xy[0], xy[1], xy); 16 | xy[0] /= kg; 17 | xy[1] *= kg; 18 | }; 19 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Tobler1.js: -------------------------------------------------------------------------------- 1 | function Tobler1() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Tobler I'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | xy[0] = lon; 9 | xy[1] = lat * (1 + lat * lat / 6); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Tobler2.js: -------------------------------------------------------------------------------- 1 | function Tobler2() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Tobler II'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | var latSqr = lat * lat; 9 | 10 | xy[0] = lon; 11 | xy[1] = lat * (1 + latSqr / 6 + latSqr * latSqr / 24); 12 | }; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/TransformableLambert.js: -------------------------------------------------------------------------------- 1 | function TransformableLambert() {"use strict"; 2 | 3 | var m, n, CA, CB, poleInsideGraticule, cosLat0; 4 | 5 | function forwardTransformableLambert(lon, lat, xy) { 6 | var sin_O, cos_O, y; 7 | 8 | // FIXME the resulting x coordinate is NaN if lon is Math.PI 9 | 10 | lon *= n; 11 | sin_O = m * Math.sin(lat); 12 | cos_O = Math.sqrt(1 - sin_O * sin_O); 13 | y = 1 + cos_O * Math.cos(lon); 14 | y = Math.sqrt(2 / y); 15 | xy[0] = CA * y * cos_O * Math.sin(lon); 16 | xy[1] = CB * y * sin_O; 17 | } 18 | 19 | function forwardCylindrical(lon, lat, xy) { 20 | xy[0] = lon * cosLat0; 21 | xy[1] = Math.sin(lat) / cosLat0; 22 | } 23 | 24 | 25 | this.initialize = function(lonLimit, latLimit, p) { 26 | var k, d, cylindrical; 27 | 28 | //lonLimit = Math.min(lonLimit, Math.PI); 29 | //latLimit = Math.min(latLimit, Math.PI / 2); 30 | 31 | cylindrical = (lonLimit < 1e-10) && (latLimit < 1e-10); 32 | if (cylindrical) { 33 | this.forward = forwardCylindrical; 34 | 35 | // standard parallel of equal-area cylindrical projection 36 | cosLat0 = Math.sqrt(p / Math.PI); 37 | } else { 38 | this.forward = forwardTransformableLambert; 39 | 40 | // FIXME 41 | lonLimit = Math.max(lonLimit, 1e-10); 42 | latLimit = Math.max(latLimit, 1e-10); 43 | 44 | m = Math.sin(latLimit); 45 | n = lonLimit / Math.PI; 46 | k = Math.sqrt(p * Math.sin(latLimit / 2) / Math.sin(lonLimit / 2)); 47 | d = Math.sqrt(m * n); 48 | CA = k / d; 49 | CB = 1 / (k * d); 50 | } 51 | 52 | poleInsideGraticule = (lonLimit === Math.PI); 53 | }; 54 | 55 | this.isPoleInsideGraticule = function() { 56 | return poleInsideGraticule; 57 | }; 58 | 59 | // FIXME this should not be required, but is currently a fix for the fact that forward() returns 60 | // NaN for the x coordinate if lon is Math.PI 61 | this.getGraticuleWidth = function() { 62 | var xy = []; 63 | this.forward(Math.PI - 1e-6, 0, xy); 64 | return xy[0] * 2; 65 | }; 66 | 67 | this.initialize(Math.PI / 2, Math.PI / 4, Math.sqrt(2)); 68 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/TransverseTransformableLambert.js: -------------------------------------------------------------------------------- 1 | function TransverseTransformableLambert() {"use strict"; 2 | 3 | var m, n, CA, CB, azimuthal; 4 | 5 | this.initialize = function(lam1, phi1, p) { 6 | var k, d; 7 | 8 | lam1 = Math.max(lam1, 0.0000001); 9 | phi1 = Math.max(phi1, 0.0000001); 10 | 11 | m = Math.sin(phi1); 12 | n = lam1 / Math.PI; 13 | k = Math.sqrt(p * Math.sin(phi1 / 2) / Math.sin(lam1 / 2)); 14 | d = Math.sqrt(m * n); 15 | CA = k / d; 16 | CB = 1 / (k * d); 17 | azimuthal = (p === 1.5); 18 | }; 19 | 20 | this.forward = function(lon, lat, xy) { 21 | 22 | var sin_O, cos_O, y, cosLon, cosLat, sinLat; 23 | 24 | // transverse 25 | lon += Math.PI / 2; 26 | cosLon = Math.cos(lon); 27 | cosLat = Math.cos(lat); 28 | lon = Math.atan2(cosLat * Math.sin(lon), Math.sin(lat)); 29 | // FIXME adjust longitude 30 | sinLat = -cosLat * cosLon; 31 | 32 | //sinLat = Math.sin(lat); 33 | 34 | lon *= n; 35 | sin_O = m * sinLat; 36 | cos_O = Math.sqrt(1 - sin_O * sin_O); 37 | y = 1 + cos_O * Math.cos(lon); 38 | y = Math.sqrt(2 / y); 39 | xy[1] = -CA * y * cos_O * Math.sin(lon); 40 | xy[0] = CB * y * sin_O; 41 | }; 42 | 43 | // FIXME 44 | this.getGraticuleWidth = function() { 45 | return 4; 46 | }; 47 | 48 | // FIXME 49 | this.getGraticuleHeight = function() { 50 | return 4; 51 | }; 52 | 53 | this.isPoleInsideGraticule = function() { 54 | return azimuthal; 55 | }; 56 | 57 | this.initialize(Math.PI / 2, Math.PI / 4, 1.5); 58 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Urmayev2.js: -------------------------------------------------------------------------------- 1 | function Urmayev2() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Urmayev II'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | var latSqr = lat * lat; 9 | 10 | xy[0] = lon; 11 | xy[1] = lat * (1 + 0.1275561329783 * latSqr + 0.0133641090422587 * latSqr * latSqr); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Urmayev3.js: -------------------------------------------------------------------------------- 1 | function Urmayev3() {"use strict"; 2 | 3 | this.toString = function() { 4 | return 'Urmayev III'; 5 | }; 6 | 7 | this.forward = function(lon, lat, xy) { 8 | var a0 = 0.92813433, a2 = 1.11426959; 9 | 10 | xy[0] = lon; 11 | xy[1] = lat * (a0 + a2 / 3 * lat * lat); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /CanvasMap/src/projections/Wagner1.js: -------------------------------------------------------------------------------- 1 | function Wagner1() {"use strict"; 2 | 3 | var C_x = 0.8773826753, 4 | Cy = 1.139753528477, 5 | n = 0.8660254037844386467637231707, 6 | C_y = Cy / n; 7 | 8 | this.toString = function() { 9 | return 'Wagner I'; 10 | }; 11 | 12 | this.forward = function(lon, lat, xy) { 13 | var lat = Math.asin(n * Math.sin(lat)); 14 | xy[0] = C_x * lon * Math.cos(lat); 15 | xy[1] = C_y * lat; 16 | }; 17 | 18 | this.inverse = function (x, y, lonlat) { 19 | y /= C_y; 20 | lonlat[1] = Math.asin(Math.sin(y) / n); 21 | lonlat[0] = x / (C_x * Math.cos(y)); 22 | }; 23 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Wagner4.js: -------------------------------------------------------------------------------- 1 | function Wagner4() {"use strict"; 2 | 3 | var MAX_ITER = 10, 4 | TOLERANCE = 1e-7, 5 | p2 = Math.PI / 3 * 2, 6 | sp = Math.sin(Math.PI / 3), 7 | r = Math.sqrt(Math.PI * 2.0 * sp / (p2 + Math.sin(p2))), 8 | cx = 2. * r / Math.PI, 9 | cy = r / sp, 10 | cp = p2 + Math.sin(p2); 11 | 12 | this.toString = function() { 13 | return 'Wagner IV'; 14 | }; 15 | 16 | this.forward = function(lon, lat, xy) { 17 | var k = cp * Math.sin(lat), v, i; 18 | for (i = MAX_ITER; i != 0; i--) { 19 | lat -= v = (lat + Math.sin(lat) - k) / (1. + Math.cos(lat)); 20 | if (Math.abs(v) < TOLERANCE) { 21 | break; 22 | } 23 | } 24 | if (i == 0) { 25 | lat = (lat < 0.) ? -Math.PI / 2 : Math.PI / 2; 26 | } else { 27 | lat *= 0.5; 28 | } 29 | xy[0] = cx * lon * Math.cos(lat); 30 | xy[1] = cy * Math.sin(lat); 31 | }; 32 | 33 | this.inverse = function (x, y, lonlat) { 34 | var lat = Math.asin(y / cy); // FIXME: test for out of bounds 35 | lonlat[0] = x / (cx * Math.cos(lat)); 36 | lat += lat; 37 | lonlat[1] = Math.asin((lat + Math.sin(lat)) / cp);// FIXME: test for out of bounds as in Proj4 38 | }; 39 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/Wagner7.js: -------------------------------------------------------------------------------- 1 | // w = 1: standard Wagner VII 2 | // w = 0: Lambert azimuthal (limiting case, not included here) 3 | // w is optional, default is w = 1 4 | function Wagner7(w) {"use strict"; 5 | 6 | var m, n, CA, CB, EPS10 = 1.e-10; 7 | 8 | this.toString = function() { 9 | return 'Wagner VII' + (w !== 1 ? ' Customized' : ''); 10 | }; 11 | 12 | this.forward = function(lon, lat, xy) { 13 | var sinO, cosO, d; 14 | 15 | sinO = m * Math.sin(lat); 16 | cosO = Math.sqrt(1 - sinO * sinO); 17 | d = Math.sqrt(2. / (1. + cosO * Math.cos(lon *= n))); 18 | xy[0] = CA * d * cosO * Math.sin(lon); 19 | xy[1] = CB * d * sinO; 20 | }; 21 | 22 | this.setW = function(weight) { 23 | var k, k2; 24 | 25 | w = weight; 26 | if (w >= 1) { 27 | w = 1; 28 | } else { 29 | if (w < EPS10) { 30 | w = EPS10; 31 | } 32 | } 33 | // constant values 34 | m = Math.sin(65 / 180 * Math.PI) * w + 1 - w; 35 | n = 1 / 3 * w + 1 - w; 36 | k = Math.sqrt((1.0745992166936477 * w + 1 - w) / Math.sin(Math.PI / 2 * n)); 37 | k2 = Math.sqrt(m * n); 38 | CA = k / k2; 39 | CB = 1 / (k * k2); 40 | }; 41 | 42 | this.setW(arguments.length === 0 ? 1 : w); 43 | } -------------------------------------------------------------------------------- /CanvasMap/src/projections/WagnerPseudocylindrical.js: -------------------------------------------------------------------------------- 1 | function WagnerPseudocylindrical() {"use strict"; 2 | 3 | var m, n, CA, CB, EPS10 = 1.e-10; 4 | 5 | this.toString = function() { 6 | return 'Wagner Pseudocylindrical'; 7 | }; 8 | 9 | this.forward = function(lon, lat, xy) { 10 | this.setW(61.9 / 180. * Math.PI, 2.03); 11 | 12 | var sinO, cosO, d; 13 | 14 | sinO = m * Math.sin(lat); 15 | cosO = Math.sqrt(1 - sinO * sinO); 16 | d = Math.sqrt(2. / (1. + cosO)); 17 | xy[0] = CA * d * cosO * lon; 18 | xy[1] = CB * d * sinO; 19 | }; 20 | 21 | this.inverse = function(x, y, lonlat) { 22 | this.setW(65. / 180. * Math.PI, 2.); 23 | 24 | sinO_2 = y / (2 * CB); 25 | cosO_2 = Math.sqrt(1 - sinO_2 * sinO_2); 26 | 27 | lonlat[0] = (x / CA) * cosO_2 / (2 * cosO_2 * cosO_2 - 1); 28 | lonlat[1] = Math.asin(2 * sinO_2 * cosO_2 / m); 29 | }; 30 | 31 | this.setW = function(latB, ratio) { 32 | 33 | if (latB < EPS10) { 34 | latB = EPS10; 35 | } 36 | 37 | m = Math.sin(latB); 38 | var k = Math.sqrt(2 * ratio * Math.sin(latB / 2.) / Math.PI); 39 | var k2 = Math.sqrt(m); 40 | CA = k / k2; 41 | CB = 1 / (k * k2); 42 | 43 | console.log('CA', CA); 44 | console.log('CB', CB); 45 | console.log('m', m); 46 | }; 47 | 48 | } -------------------------------------------------------------------------------- /CanvasMap/src/shapefile.js: -------------------------------------------------------------------------------- 1 | // ported from http://code.google.com/p/vanrijkom-flashlibs/ under LGPL v2.1 2 | 3 | function ShpFile(binFile) { 4 | 5 | var src = new BinaryFileWrapper(binFile); 6 | 7 | var t1 = new Date().getTime(); 8 | this.header = new ShpHeader(src); 9 | 10 | var t2 = new Date().getTime(); 11 | //if (window.console && window.console.log) console.log('parsed header in ' + (t2-t1) + ' ms'); 12 | 13 | //if (window.console && window.console.log) console.log('got header, parsing records'); 14 | 15 | t1 = new Date().getTime(); 16 | this.records = []; 17 | while (true) { 18 | try { 19 | this.records.push(new ShpRecord(src)); 20 | } catch (e) { 21 | if (e.id !== ShpError.ERROR_NODATA) { 22 | alert(e); 23 | } 24 | break; 25 | } 26 | } 27 | 28 | t2 = new Date().getTime(); 29 | //if (window.console && window.console.log) console.log('parsed records in ' + (t2-t1) + ' ms'); 30 | 31 | } 32 | 33 | /** 34 | * The ShpType class is a place holder for the ESRI Shapefile defined 35 | * shape types. 36 | * @author Edwin van Rijkom 37 | * 38 | */ 39 | var ShpType = { 40 | 41 | /** 42 | * Unknow Shape Type (for internal use) 43 | */ 44 | SHAPE_UNKNOWN : -1, 45 | /** 46 | * ESRI Shapefile Null Shape shape type. 47 | */ 48 | SHAPE_NULL : 0, 49 | /** 50 | * ESRI Shapefile Point Shape shape type. 51 | */ 52 | SHAPE_POINT : 1, 53 | /** 54 | * ESRI Shapefile PolyLine Shape shape type. 55 | */ 56 | SHAPE_POLYLINE : 3, 57 | /** 58 | * ESRI Shapefile Polygon Shape shape type. 59 | */ 60 | SHAPE_POLYGON : 5, 61 | /** 62 | * ESRI Shapefile Multipoint Shape shape type 63 | * (currently unsupported). 64 | */ 65 | SHAPE_MULTIPOINT : 8, 66 | /** 67 | * ESRI Shapefile PointZ Shape shape type. 68 | */ 69 | SHAPE_POINTZ : 11, 70 | /** 71 | * ESRI Shapefile PolylineZ Shape shape type 72 | * (currently unsupported). 73 | */ 74 | SHAPE_POLYLINEZ : 13, 75 | /** 76 | * ESRI Shapefile PolygonZ Shape shape type 77 | * (currently unsupported). 78 | */ 79 | SHAPE_POLYGONZ : 15, 80 | /** 81 | * ESRI Shapefile MultipointZ Shape shape type 82 | * (currently unsupported). 83 | */ 84 | SHAPE_MULTIPOINTZ : 18, 85 | /** 86 | * ESRI Shapefile PointM Shape shape type 87 | */ 88 | SHAPE_POINTM : 21, 89 | /** 90 | * ESRI Shapefile PolyLineM Shape shape type 91 | * (currently unsupported). 92 | */ 93 | SHAPE_POLYLINEM : 23, 94 | /** 95 | * ESRI Shapefile PolygonM Shape shape type 96 | * (currently unsupported). 97 | */ 98 | SHAPE_POLYGONM : 25, 99 | /** 100 | * ESRI Shapefile MultiPointM Shape shape type 101 | * (currently unsupported). 102 | */ 103 | SHAPE_MULTIPOINTM : 28, 104 | /** 105 | * ESRI Shapefile MultiPatch Shape shape type 106 | * (currently unsupported). 107 | */ 108 | SHAPE_MULTIPATCH : 31 109 | 110 | }; 111 | 112 | /** 113 | * Constructor. 114 | * @param src 115 | * @return 116 | * @throws ShpError Not a valid shape file header 117 | * @throws ShpError Not a valid signature 118 | * 119 | */ 120 | function ShpHeader(src) { 121 | if (src.getLength() < 100) 122 | alert("Not a valid shape file header (too small)"); 123 | 124 | if (src.getSLong() != 9994) 125 | alert("Not a valid signature. Expected 9994"); 126 | 127 | // skip 5 integers; 128 | src.position += 5 * 4; 129 | 130 | // read file-length: 131 | this.fileLength = src.getSLong(); 132 | 133 | // switch endian: 134 | src.bigEndian = false; 135 | 136 | // read version: 137 | this.version = src.getSLong(); 138 | 139 | // read shape-type: 140 | this.shapeType = src.getSLong(); 141 | 142 | // read bounds: 143 | this.boundsXY = { 144 | x : src.getDouble(), 145 | y : src.getDouble(), 146 | width : src.getDouble(), 147 | height : src.getDouble() 148 | }; 149 | 150 | this.boundsZ = { 151 | x : src.getDouble(), 152 | y : src.getDouble() 153 | }; 154 | 155 | this.boundsM = { 156 | x : src.getDouble(), 157 | y : src.getDouble() 158 | }; 159 | } 160 | 161 | function ShpRecord(src) { 162 | var availableBytes = src.getLength() - src.position; 163 | 164 | if (availableBytes == 0) 165 | throw (new ShpError("No Data", ShpError.ERROR_NODATA)); 166 | 167 | if (availableBytes < 8) 168 | throw (new ShpError("Not a valid record header (too small)")); 169 | 170 | src.bigEndian = true; 171 | 172 | this.number = src.getSLong(); 173 | this.contentLength = src.getSLong(); 174 | this.contentLengthBytes = this.contentLength * 2 - 4; 175 | src.bigEndian = false; 176 | var shapeOffset = src.position; 177 | this.shapeType = src.getSLong(); 178 | 179 | switch(this.shapeType) { 180 | case ShpType.SHAPE_POINT: 181 | this.shape = new ShpPoint(src, this.contentLengthBytes); 182 | break; 183 | case ShpType.SHAPE_POINTZ: 184 | this.shape = new ShpPointZ(src, this.contentLengthBytes); 185 | break; 186 | case ShpType.SHAPE_POLYGON: 187 | this.shape = new ShpPolygon(src, this.contentLengthBytes); 188 | break; 189 | case ShpType.SHAPE_POLYLINE: 190 | this.shape = new ShpPolyline(src, this.contentLengthBytes); 191 | break; 192 | case ShpType.SHAPE_MULTIPATCH: 193 | case ShpType.SHAPE_MULTIPOINT: 194 | case ShpType.SHAPE_MULTIPOINTM: 195 | case ShpType.SHAPE_MULTIPOINTZ: 196 | case ShpType.SHAPE_POINTM: 197 | case ShpType.SHAPE_POLYGONM: 198 | case ShpType.SHAPE_POLYGONZ: 199 | case ShpType.SHAPE_POLYLINEZ: 200 | case ShpType.SHAPE_POLYLINEM: 201 | throw (new ShpError(this.shapeType + " Shape type is currently unsupported by this library")); 202 | break; 203 | default: 204 | throw (new ShpError("Encountered unknown shape type (" + this.shapeType + ")")); 205 | break; 206 | } 207 | } 208 | 209 | function ShpPoint(src, size) { 210 | this.type = ShpType.SHAPE_POINT; 211 | if (src) { 212 | if (src.getLength() - src.position < size) 213 | throw (new ShpError("Not a Point record (too small)")); 214 | this.x = (size > 0) ? src.getDouble() : NaN; 215 | this.y = (size > 0) ? src.getDouble() : NaN; 216 | } 217 | } 218 | 219 | function ShpPointZ(src, size) { 220 | this.type = ShpType.SHAPE_POINTZ; 221 | if (src) { 222 | if (src.getLength() - src.position < size) 223 | throw (new ShpError("Not a Point record (too small)")); 224 | this.x = (size > 0) ? src.getDouble() : NaN; 225 | this.y = (size > 0) ? src.getDouble() : NaN; 226 | this.z = (size > 16) ? src.getDouble() : NaN; 227 | this.m = (size > 24) ? src.getDouble() : NaN; 228 | } 229 | } 230 | 231 | function ShpPolygon(src, size) { 232 | // for want of a super() 233 | ShpPolyline.apply(this, [src, size]); 234 | this.type = ShpType.SHAPE_POLYGON; 235 | } 236 | 237 | function ShpPolyline(src, size) { 238 | this.type = ShpType.SHAPE_POLYLINE; 239 | this.rings = []; 240 | if (src) { 241 | if (src.getLength() - src.position < size) 242 | throw (new ShpError("Not a Polygon record (too small)")); 243 | 244 | src.bigEndian = false; 245 | 246 | this.box = { 247 | x : src.getDouble(), 248 | y : src.getDouble(), 249 | width : src.getDouble(), 250 | height : src.getDouble() 251 | }; 252 | 253 | var rc = src.getSLong(); 254 | var pc = src.getSLong(); 255 | 256 | var ringOffsets = []; 257 | while (rc--) { 258 | var ringOffset = src.getSLong(); 259 | ringOffsets.push(ringOffset); 260 | } 261 | 262 | var points = []; 263 | while (pc--) { 264 | points.push(new ShpPoint(src, 16)); 265 | } 266 | 267 | // convert points, and ringOffsets arrays to an array of rings: 268 | var removed = 0; 269 | var split; 270 | ringOffsets.shift(); 271 | while (ringOffsets.length) { 272 | split = ringOffsets.shift(); 273 | this.rings.push(points.splice(0, split - removed)); 274 | removed = split; 275 | } 276 | this.rings.push(points); 277 | } 278 | } 279 | 280 | function ShpError(msg, id) { 281 | this.msg = msg; 282 | this.id = id; 283 | this.toString = function() { 284 | return this.msg; 285 | }; 286 | } 287 | 288 | ShpError.ERROR_UNDEFINED = 0; 289 | // a 'no data' error is thrown when the byte array runs out of data. 290 | ShpError.ERROR_NODATA = 1; 291 | -------------------------------------------------------------------------------- /CentralMeridianSlider/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, NaturalEarth, createMap*/ 2 | 3 | var map; 4 | 5 | function getMapScale(map) {"use strict"; 6 | var K = 0.90; 7 | return Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()) * K; 8 | } 9 | 10 | function writeSliderValue(nbr) {"use strict"; 11 | $("#meridianText").text(Math.abs(Math.round(nbr)) + "\u00B0" + (nbr < 0 ? "W" : "E")); 12 | } 13 | 14 | function mouseEventHandler(e) {"use strict"; 15 | 16 | var mouseMove, mouseUp, prevMouse; 17 | 18 | prevMouse = { 19 | x : e.clientX, 20 | y : e.clientY 21 | }; 22 | 23 | mouseMove = function(e) { 24 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 25 | 26 | unitsPerPixel = 1 / getMapScale(map); 27 | lon0 = map.getCentralLongitude(); 28 | lon0 -= dx * unitsPerPixel; 29 | 30 | map.setCentralLongitude(lon0); 31 | 32 | $("#meridianSlider").slider('value', lon0 / Math.PI * 180); 33 | writeSliderValue(lon0 / Math.PI * 180); 34 | 35 | prevMouse.x = e.clientX; 36 | prevMouse.y = e.clientY; 37 | e.preventDefault(); 38 | }; 39 | 40 | mouseUp = function(e) { 41 | document.body.style.cursor = null; 42 | document.removeEventListener('mousemove', mouseMove, false); 43 | document.removeEventListener('mouseup', mouseUp, false); 44 | }; 45 | 46 | document.body.style.cursor = 'move'; 47 | document.addEventListener('mousemove', mouseMove, false); 48 | document.addEventListener('mouseup', mouseUp, false); 49 | } 50 | 51 | function initSlider() {"use strict"; 52 | 53 | $(function() { 54 | 55 | function action(event, ui) { 56 | // fix a bug in jQuery slider 57 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 58 | $("#meridianSlider").slider('value', ui.value); 59 | writeSliderValue(ui.value); 60 | map.setCentralLongitude(ui.value / 180 * Math.PI); 61 | } 62 | 63 | // create the meridian slider 64 | $("#meridianSlider").slider({ 65 | orientation : "horizontal", 66 | range : "min", 67 | min : -180, 68 | max : 180, 69 | value : 0, 70 | step : 1, 71 | slide : action 72 | }); 73 | writeSliderValue(0); 74 | }); 75 | } 76 | 77 | 78 | $(window).load(function() {"use strict"; 79 | 80 | // currently styling works like this: 81 | // - if there's a fillStyle then it will be filled 82 | // - if there's a strokeStyle then it will be stroked 83 | // - points are always 3px rectangles 84 | // - polylines can't be filled 85 | 86 | var layers = []; 87 | 88 | function Style(strokeStyle, lineWidth, fillStyle) { 89 | if (strokeStyle !== undefined) { 90 | this.strokeStyle = strokeStyle; 91 | this.lineWidth = lineWidth; 92 | } 93 | if (fillStyle !== undefined) { 94 | this.fillStyle = fillStyle; 95 | } 96 | } 97 | 98 | //create the map 99 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 100 | layers.push(new Graticule(new Style("#77b", "1"), 15)); 101 | 102 | map = createMap(layers, new NaturalEarth(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 103 | $(window).resize(function() { 104 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 105 | map.setScale(getMapScale(map)); 106 | }); 107 | 108 | map.setScale(getMapScale(map)); 109 | 110 | // init UI controls 111 | $("#map").bind("mousedown", mouseEventHandler); 112 | 113 | initSlider(); 114 | 115 | // ie 116 | $("#mapCanvas")[0].onselectstart = function() { 117 | return false; 118 | }; 119 | // mozilla 120 | $("#mapCanvas")[0].onmousedown = function() { 121 | return false; 122 | }; 123 | 124 | }); 125 | 126 | -------------------------------------------------------------------------------- /CentralMeridianSlider/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapCanvas Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /CentralMeridianSlider/static.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eee; 3 | font: 12px sans-serif; 4 | } 5 | 6 | .map { 7 | height:500px; 8 | width:100%; 9 | padding:0px; 10 | border:0px; 11 | background-color: #fff; 12 | position:relative; 13 | } 14 | 15 | .mapCanvas { 16 | width: 100%; 17 | height: 100%; 18 | position:absolute; 19 | } 20 | 21 | #meridianContainer { 22 | position: relative; 23 | margin-left:auto; 24 | margin-right:auto; 25 | width:380px; 26 | } 27 | 28 | #meridianSlider{ 29 | position: relative; 30 | width:300px; 31 | float: left; 32 | } 33 | 34 | #meridianText{ 35 | position: relative; 36 | width: 70px; 37 | margin-left: 5px; 38 | float: left; 39 | } -------------------------------------------------------------------------------- /Hufnagel/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Layer, Graticule, $, Hufnagel, createMap */ 2 | 3 | var map; 4 | 5 | function deselectButtons() { 6 | "use strict"; 7 | $('#selectable .ui-selected').removeClass('ui-selected'); 8 | $('#selectable .ui-selecting').removeClass('ui-selecting'); 9 | } 10 | 11 | function updateProjection() { 12 | "use strict"; 13 | var projection = new Hufnagel(); 14 | projection.setA($("#sliderA").slider("value") / 100); 15 | projection.setB($("#sliderB").slider("value") / 100); 16 | projection.setPsiMax($("#sliderPsi").slider("value") / 180 * Math.PI); 17 | projection.setAspectRatio($("#sliderAspect").slider("value") / 100); 18 | map.setProjection(projection); 19 | if (projection.isGraticuleFolding()) { 20 | $("#foldingWarning").show(); 21 | } else { 22 | $("#foldingWarning").hide(); 23 | } 24 | } 25 | 26 | function updateSliderTexts() { 27 | "use strict"; 28 | var a, b, psi, aspectRatio; 29 | a = $("#sliderA").slider("value") / 100; 30 | b = $("#sliderB").slider("value") / 100; 31 | psi = $("#sliderPsi").slider("value"); 32 | aspectRatio = $("#sliderAspect").slider("value") / 100; 33 | $("#sliderAText").text(a.toFixed(4)); 34 | $("#sliderBText").text(b.toFixed(4)); 35 | $("#sliderPsiText").text(psi.toFixed(0) + "\u00B0"); 36 | $("#sliderAspectText").text(aspectRatio.toFixed(2)); 37 | } 38 | 39 | function setProjection(selectedItem) { 40 | "use strict"; 41 | 42 | var a, b, psi, aspectRatio = 2; 43 | 44 | switch (selectedItem) { 45 | case "projectionMollweide": 46 | a = 0; 47 | b = 0; 48 | psi = 90; 49 | break; 50 | case "projectionEckertIV": 51 | a = 1; 52 | b = 0; 53 | psi = 45; 54 | break; 55 | case "projectionEckertVI": 56 | a = -0.09524; 57 | b = 0.09524; 58 | psi = 60; 59 | break; 60 | case "projectionMollweideWagner": 61 | a = 0; 62 | b = 0; 63 | psi = 60; 64 | break; 65 | case "projectionHufnagel2": 66 | a = 0.05556; 67 | b = -0.05556; 68 | psi = 90; 69 | break; 70 | case "projectionHufnagel3": 71 | a = 0.5; 72 | b = 0.05556; 73 | psi = 90; 74 | break; 75 | case "projectionHufnagel4": 76 | a = 0.08333; 77 | b = -0.08333; 78 | psi = 90; 79 | break; 80 | case "projectionHufnagel7": 81 | a = 0.08333; 82 | b = -0.08333; 83 | psi = 60; 84 | break; 85 | case "projectionHufnagel9": 86 | a = 0.66667; 87 | b = 0.33333; 88 | psi = 45; 89 | break; 90 | case "projectionHufnagel10": 91 | a = -0.66667; 92 | b = 0.66667; 93 | psi = 30; 94 | break; 95 | case "projectionHufnagel11": 96 | a = 0; 97 | b = -0.11111; 98 | psi = 90; 99 | break; 100 | case "projectionHufnagel12": 101 | a = 0; 102 | b = -0.11111; 103 | psi = 40; 104 | aspectRatio = 1 / 0.4; 105 | break; 106 | case "projectionCylindrical": 107 | a = NaN; 108 | b = NaN; 109 | psi = 1; 110 | aspectRatio = NaN; 111 | break; 112 | } 113 | 114 | if (!isNaN(a)) { 115 | $("#sliderA").slider("value", a * 100); 116 | } 117 | if (!isNaN(b)) { 118 | $("#sliderB").slider("value", b * 100); 119 | } 120 | if (!isNaN(psi)) { 121 | $("#sliderPsi").slider("value", psi); 122 | } 123 | if (!isNaN(aspectRatio)) { 124 | $("#sliderAspect").slider("value", aspectRatio * 100); 125 | } 126 | 127 | updateSliderTexts(); 128 | updateProjection(); 129 | } 130 | 131 | $(function() { 132 | "use strict"; 133 | 134 | var mousedown = false, firstRun = true, last_selectedItem_id; 135 | $('#selectable').selectable({ 136 | start : function(event, ui) { 137 | mousedown = true; 138 | }, 139 | stop : function(event, ui) { 140 | //Here the event ends, so that we can remove the selected class to all but the one we want 141 | $(event.target).children('.ui-selected').not("#" + last_selectedItem_id).removeClass('ui-selected'); 142 | mousedown = false; 143 | }, 144 | //a special case is the first run, 145 | //$(".ui-selected, .ui-selecting").length is considered to be 2 146 | selecting : function(event, ui) { 147 | if (($(".ui-selected, .ui-selecting").length > 1 ) && firstRun === false) { 148 | $(event.target).children('.ui-selecting').not(':first').removeClass('ui-selecting'); 149 | $(ui.selecting).removeClass("ui-selecting"); 150 | } else { 151 | setProjection(ui.selecting.id); 152 | last_selectedItem_id = ui.selecting.id; 153 | if (firstRun) { 154 | firstRun = false; 155 | } 156 | } 157 | } 158 | }); 159 | }); 160 | 161 | function mouseEventHandler(e) { 162 | "use strict"; 163 | 164 | var mouseMove, mouseUp, prevMouse; 165 | 166 | prevMouse = { 167 | x : e.clientX, 168 | y : e.clientY 169 | }; 170 | 171 | mouseMove = function(e) { 172 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 173 | 174 | unitsPerPixel = 1 / map.getScale(); 175 | lon0 = map.getCentralLongitude(); 176 | lon0 -= dx * unitsPerPixel; 177 | map.setCentralLongitude(lon0); 178 | 179 | prevMouse.x = e.clientX; 180 | prevMouse.y = e.clientY; 181 | e.preventDefault(); 182 | }; 183 | 184 | mouseUp = function(e) { 185 | document.body.style.cursor = null; 186 | document.removeEventListener('mousemove', mouseMove, false); 187 | document.removeEventListener('mouseup', mouseUp, false); 188 | }; 189 | 190 | document.body.style.cursor = 'move'; 191 | document.addEventListener('mousemove', mouseMove, false); 192 | document.addEventListener('mouseup', mouseUp, false); 193 | } 194 | 195 | function initASlider() { 196 | "use strict"; 197 | 198 | function action(event, ui) { 199 | // fix a bug in jQuery slider 200 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 201 | $("#sliderA").slider('value', ui.value); 202 | updateSliderTexts(); 203 | updateProjection(); 204 | deselectButtons(); 205 | } 206 | 207 | // create the slider 208 | $("#sliderA").slider({ 209 | orientation : "horizontal", 210 | range : "min", 211 | min : -100, 212 | max : 100, 213 | value : 0, 214 | step : 0.1, 215 | slide : action 216 | }); 217 | } 218 | 219 | function initBSlider() { 220 | "use strict"; 221 | function action(event, ui) { 222 | // fix a bug in jQuery slider 223 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 224 | $("#sliderB").slider('value', ui.value); 225 | updateSliderTexts(); 226 | updateProjection(); 227 | deselectButtons(); 228 | } 229 | 230 | // create the slider 231 | $("#sliderB").slider({ 232 | orientation : "horizontal", 233 | range : "min", 234 | min : -100, 235 | max : 100, 236 | value : 0, 237 | step : 0.1, 238 | slide : action 239 | }); 240 | } 241 | 242 | function initPsiSlider() { 243 | "use strict"; 244 | function action(event, ui) { 245 | // fix a bug in jQuery slider 246 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 247 | $("#sliderPsi").slider('value', ui.value); 248 | updateSliderTexts(); 249 | updateProjection(); 250 | deselectButtons(); 251 | } 252 | 253 | // create the slider 254 | $("#sliderPsi").slider({ 255 | orientation : "horizontal", 256 | range : "min", 257 | min : 0, 258 | max : 90, 259 | value : 90, 260 | step : 1, 261 | slide : action 262 | }); 263 | } 264 | 265 | function initAspectSlider() { 266 | "use strict"; 267 | function action(event, ui) { 268 | // fix a bug in jQuery slider 269 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 270 | $("#sliderAspect").slider('value', ui.value); 271 | updateSliderTexts(); 272 | updateProjection(); 273 | deselectButtons(); 274 | } 275 | 276 | // create the slider 277 | $("#sliderAspect").slider({ 278 | orientation : "horizontal", 279 | range : "min", 280 | min : 100, 281 | max : 275, 282 | value : 200, 283 | step : 1, 284 | slide : action 285 | }); 286 | } 287 | 288 | function getMapScale(map) { 289 | "use strict"; 290 | 291 | var K = 0.95, aspectRatio = $("#sliderAspect").slider("value") / 100; 292 | if (aspectRatio > map.getCanvas().width / map.getCanvas().height) { 293 | return map.getCanvas().width / (2 * Math.PI) * K; 294 | } 295 | return map.getCanvas().height / Math.PI * K; 296 | } 297 | 298 | 299 | $(window).load(function() { 300 | "use strict"; 301 | // currently styling works like this: 302 | // - if there's a fillStyle then it will be filled 303 | // - if there's a strokeStyle then it will be stroked 304 | // - points are always 3px rectangles 305 | // - polylines can't be filled 306 | 307 | var layers, graticule; 308 | 309 | function Style(strokeStyle, lineWidth, fillStyle) { 310 | if (strokeStyle !== undefined) { 311 | this.strokeStyle = strokeStyle; 312 | this.lineWidth = lineWidth; 313 | } 314 | if (fillStyle !== undefined) { 315 | this.fillStyle = fillStyle; 316 | } 317 | } 318 | 319 | //create the map 320 | graticule = new Graticule(new Style("#77b", "1"), 15, 1); 321 | layers = []; 322 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 323 | layers.push(graticule); 324 | 325 | map = createMap(layers, new Hufnagel(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 326 | 327 | $(window).resize(function() { 328 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 329 | map.setScale(getMapScale(map)); 330 | }); 331 | 332 | $("#map").bind("mousedown", mouseEventHandler); 333 | 334 | initASlider(); 335 | initBSlider(); 336 | initPsiSlider(); 337 | initAspectSlider(); 338 | updateSliderTexts(); 339 | updateProjection(); 340 | map.setScale(getMapScale(map)); 341 | }); 342 | -------------------------------------------------------------------------------- /Hufnagel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hufnagel Projection Family 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Hufnagel Equal-Area Pseudocylindrical Map Projection Family

24 |

25 | In 1989, Herbert Hufnagel introduced a generalization of the Mollweide projection, resulting in a family of pseudocylindrical equal-area projections. The Mollweide, Eckert Ⅳ and Wagner Ⅳ projections are members of this family. 26 |

27 |

28 | All projections are equal-area. Some paramter combinations create folding graticules. 29 |

30 |

31 | Source: Hufnagel, H. 1989. Ein System unecht-zylindrischer Kartennetze für Erdkarten. Kartographische Nachrichten, 39(3), 89–96. 32 |

33 |

34 | Programming by Bernie Jenny (Oregon State University and RMIT Melbourne), Bojan Šavrič (Oregon State University and Esri Inc.) and Daniel "daan" Strebe, Mapthematics. © Dec 2014–October 2015. 35 |

36 | 37 |
38 | 39 |
40 |
41 | A 42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 | B 51 |
52 | 53 |
54 |
55 |
56 |
57 | 58 |
59 | Ψmax 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 | Length Ratio between Equator and Central Meridian 68 |
69 |
70 |
71 |
72 |
73 | 74 |
    75 |
  1. 76 | Mollweide 77 |
  2. 78 |
  3. 79 | Eckert Ⅳ 80 |
  4. 81 |
  5. 82 | Eckert Ⅵ (approximate) 83 |
  6. 84 |
  7. 85 | Mollweide-Wagner 86 |
  8. 87 |
  9. 88 | Hufnagel Ⅱ 89 |
  10. 90 |
  11. 91 | Hufnagel Ⅲ 92 |
  12. 93 |
  13. 94 | Hufnagel Ⅳ 95 |
  14. 96 |
  15. 97 | Hufnagel Ⅶ 98 |
  16. 99 |
  17. 100 | Hufnagel Ⅸ 101 |
  18. 102 |
  19. 103 | Hufnagel Ⅹ 104 |
  20. 105 |
  21. 106 | Hufnagel Ⅺ 107 |
  22. 108 |
  23. 109 | Hufnagel Ⅻ 110 |
  24. 111 |
  25. 112 | Cylindrical Equal-Area 113 |
  26. 114 |
115 | 116 |
117 | Graticule is folding. 118 |
119 |
120 | 121 |
122 | 123 |
124 |
125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /Hufnagel/projection-selection-list.css: -------------------------------------------------------------------------------- 1 | #foldingWarning { 2 | color:red; 3 | margin-top: 12px; 4 | margin: 6px; 5 | display: none; 6 | } 7 | 8 | #StParallel { 9 | margin-bottom: 30px; 10 | } 11 | 12 | #selectable { 13 | list-style-type: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | #feedback { 18 | font-size: 1.4em; 19 | } 20 | #selectable .ui-selecting { 21 | background: #BBBBBB; 22 | } 23 | #selectable .ui-selected { 24 | background: #BBBBBB; 25 | color: white; 26 | } 27 | #selectable li { 28 | margin-top: 3px; 29 | margin-bottom: 3px; 30 | padding: 0.4em; 31 | width: 195px; 32 | height: 18px; 33 | } -------------------------------------------------------------------------------- /LambertTransition/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Layer, Graticule, $, TransformableLambert, createMap */ 2 | 3 | var map, 4 | 5 | //true when projection is animated 6 | animateFlag = false, 7 | 8 | // id for stopping the animation 9 | animationIntervalId, 10 | 11 | // frames per second for animations 12 | ANIMATION_FPS = 20, 13 | 14 | // the duration of an animation in seconds 15 | ANIMATION_DURATION = 1, 16 | 17 | // the number of frames for one animation 18 | ANIMATION_NBR_FRAMES = Math.round(ANIMATION_DURATION * 1000 / ANIMATION_FPS); 19 | 20 | function selectSelectableElement(selectableContainer, elementsToSelect) {"use strict"; 21 | // add unselecting class to all elements in the styleboard canvas except the ones to select 22 | $(".ui-selected", selectableContainer).not(elementsToSelect).removeClass("ui-selected").addClass("ui-unselecting"); 23 | 24 | // add ui-selecting class to the elements to select 25 | $(elementsToSelect).not(".ui-selected").addClass("ui-selecting"); 26 | 27 | // trigger the mouse stop event (this will select all .ui-selecting elements, and deselect all .ui-unselecting elements) 28 | $('#selectable').selectable("option", "stop"); 29 | } 30 | 31 | function updateProjection() {"use strict"; 32 | var lat0, w, lam1, phi1, pCyl, p, phi0, projection; 33 | 34 | if (!animateFlag) { 35 | if ($("#sliderBoundingParallel").slider("value") === 90 && $("#sliderBoundingMeridian").slider("value") === 180 && $("#sliderEquator").slider("value") === 1.41) { 36 | selectSelectableElement($("#selectable"), $("li:first", "#selectable")); 37 | } else if ($("#sliderBoundingParallel").slider("value") === 90 && $("#sliderBoundingMeridian").slider("value") === 90 && $("#sliderEquator").slider("value") === 2) { 38 | selectSelectableElement($("#selectable"), $("li:nth-child(2)", "#selectable")); 39 | } else if ($("#sliderBoundingParallel").slider("value") === 90 && $("#sliderBoundingMeridian").slider("value") === 45 && $("#sliderEquator").slider("value") === 2.16) { 40 | selectSelectableElement($("#selectable"), $("li:nth-child(3)", "#selectable")); 41 | } else if ($("#sliderBoundingParallel").slider("value") === 90 && $("#sliderBoundingMeridian").slider("value") === 0 && $("#sliderEquator").slider("value") === 2.22) { 42 | selectSelectableElement($("#selectable"), $("li:nth-child(4)", "#selectable")); 43 | } else if ($("#sliderBoundingParallel").slider("value") === 65 && $("#sliderBoundingMeridian").slider("value") === 60 && $("#sliderEquator").slider("value") === 2) { 44 | selectSelectableElement($("#selectable"), $("li:nth-child(5)", "#selectable")); 45 | } else if ($("#sliderBoundingParallel").slider("value") === 0 && $("#sliderBoundingMeridian").slider("value") === 0 && $("#sliderEquator").slider("value") === 3.14) { 46 | selectSelectableElement($("#selectable"), $("li:nth-child(6)", "#selectable")); 47 | } else if ($("#sliderBoundingParallel").slider("value") === 62 && $("#sliderBoundingMeridian").slider("value") === 0 && $("#sliderEquator").slider("value") === 2.03) { 48 | selectSelectableElement($("#selectable"), $("li:last", "#selectable")); 49 | } else { 50 | $('#selectable .ui-selected').removeClass('ui-selected'); 51 | $('#selectable .ui-selecting').removeClass('ui-selecting'); 52 | } 53 | } 54 | 55 | p = $("#sliderEquator").slider("value"); 56 | phi1 = $("#sliderBoundingParallel").slider("value") / 180 * Math.PI; 57 | lam1 = $("#sliderBoundingMeridian").slider("value") / 180 * Math.PI; 58 | 59 | projection = new TransformableLambert(); 60 | 61 | phi0 = Math.acos(Math.sqrt(p / Math.PI)); 62 | phi0 = phi0 * 180 / Math.PI; 63 | 64 | if (parseFloat(phi1.toFixed(1)) === 0 && parseFloat(lam1.toFixed(1)) === 0) { 65 | if (parseFloat(p.toFixed(2)) < 3.14) { 66 | //$('#StParallel').show(); 67 | $("#StParallel").html('Cylindrical projection with standard parallels at ±' + phi0.toFixed(1) + "\u00B0" + "."); 68 | } else if (parseFloat(p.toFixed(2)) === 3.14) { 69 | //$('#StParallel').show(); 70 | $("#StParallel").html('Cylindrical projection with standard parallels at ' + "0" + "\u00B0" + "."); 71 | } else if (parseFloat(p.toFixed(2)) > 3.14) { 72 | //$('#StParallel').hide(); 73 | $("#StParallel").html(' '); 74 | } 75 | } else { 76 | //$('#StParallel').hide(); 77 | $("#StParallel").html(' '); 78 | } 79 | projection.initialize(lam1, phi1, p); 80 | map.setProjection(projection); 81 | } 82 | 83 | function updateSliderTexts() {"use strict"; 84 | $("#sliderBoundingParallelText").text($("#sliderBoundingParallel").slider('value').toFixed(0) + "\u00B0"); 85 | $("#sliderBoundingMeridianText").text($("#sliderBoundingMeridian").slider('value').toFixed(0) + "\u00B0"); 86 | $("#sliderEquatorText").text($("#sliderEquator").slider('value').toFixed(2)); 87 | } 88 | 89 | function animate(targetBoundingParallel, targetBoundingMeridian, targetEquator) {"use strict"; 90 | 91 | var dBoundingParallel, dBoundingMeridian, dEquator, frameCounter = 1; 92 | function animateSliders() { 93 | 94 | $("#sliderBoundingParallel").slider("value", targetBoundingParallel - (ANIMATION_NBR_FRAMES - frameCounter) * dBoundingParallel); 95 | $("#sliderBoundingMeridian").slider("value", targetBoundingMeridian - (ANIMATION_NBR_FRAMES - frameCounter) * dBoundingMeridian); 96 | $("#sliderEquator").slider("value", targetEquator - (ANIMATION_NBR_FRAMES - frameCounter) * dEquator); 97 | 98 | //console.log(targetBoundingParallel - (ANIMATION_NBR_FRAMES - frameCounter) * dBoundingParallel, targetBoundingMeridian - (ANIMATION_NBR_FRAMES - frameCounter) * dBoundingMeridian, targetEquator - (ANIMATION_NBR_FRAMES - frameCounter) * dEquator); 99 | 100 | updateSliderTexts(); 101 | updateProjection(); 102 | 103 | frameCounter += 1; 104 | if (frameCounter > ANIMATION_NBR_FRAMES) { 105 | clearInterval(animationIntervalId); 106 | } 107 | } 108 | 109 | // compute the change/step for each slider 110 | dBoundingParallel = (targetBoundingParallel - $("#sliderBoundingParallel").slider("value") ) / ANIMATION_NBR_FRAMES; 111 | dBoundingMeridian = (targetBoundingMeridian - $("#sliderBoundingMeridian").slider("value") ) / ANIMATION_NBR_FRAMES; 112 | dEquator = (targetEquator - $("#sliderEquator").slider("value") ) / ANIMATION_NBR_FRAMES; 113 | 114 | // calls projection at specified intervals (milliseconds) 115 | animationIntervalId = setInterval(animateSliders, 1 / ANIMATION_FPS); 116 | } 117 | 118 | function animateHelper(selectedItem) {"use strict"; 119 | 120 | var targetBoundingParallel, targetBoundingMeridian, targetEquator; 121 | 122 | animateFlag = true; 123 | clearInterval(animationIntervalId); 124 | //selectedItem = $('#selectable .ui-selecting').attr('id'); 125 | //selectedItem = $('#selectable .ui-selected').attr('id'); 126 | 127 | if (selectedItem === "projectionLambertAzimuthalEA") { 128 | targetBoundingParallel = 90; 129 | targetBoundingMeridian = 180; 130 | targetEquator = Math.sqrt(2); 131 | } else if (selectedItem === "projectionHammer") { 132 | targetBoundingParallel = 90; 133 | targetBoundingMeridian = 90; 134 | targetEquator = 2; 135 | } else if (selectedItem === "projectionEckertGreifendorff") { 136 | targetBoundingParallel = 90; 137 | targetBoundingMeridian = 45; 138 | targetEquator = 4 * Math.sqrt(2) * Math.sin(22.5 * Math.PI / 180); 139 | } else if (selectedItem === "projectionQuarticAuthalic") { 140 | targetBoundingParallel = 90; 141 | targetBoundingMeridian = 0; 142 | targetEquator = (Math.sqrt(2) / 2) * Math.PI; 143 | } else if (selectedItem === "projectionWagnerVII") { 144 | targetBoundingParallel = 65; 145 | targetBoundingMeridian = 60; 146 | targetEquator = 2; 147 | } else if (selectedItem === "projectionLambertCylindricalEA") { 148 | targetBoundingParallel = 0; 149 | targetBoundingMeridian = 0; 150 | targetEquator = Math.PI; 151 | } else if (selectedItem === "projectionPseudocylindrical") { 152 | targetBoundingParallel = 62; 153 | targetBoundingMeridian = 0; 154 | targetEquator = 2.03; 155 | } 156 | 157 | animate(targetBoundingParallel, targetBoundingMeridian, targetEquator); 158 | } 159 | 160 | $(function() {"use strict"; 161 | 162 | var mousedown = false, firstRun = true, last_selectedItem_id; 163 | $('#selectable').selectable({ 164 | start : function(event, ui) { 165 | mousedown = true; 166 | }, 167 | stop : function(event, ui) { 168 | //Here the event ends, so that we can remove the selected class to all but the one we want 169 | $(event.target).children('.ui-selected').not("#" + last_selectedItem_id).removeClass('ui-selected'); 170 | mousedown = false; 171 | }, 172 | //a special case is the first run, 173 | //$(".ui-selected, .ui-selecting").length is considered to be 2 174 | selecting : function(event, ui) { 175 | if (($(".ui-selected, .ui-selecting").length > 1 ) && firstRun === false) { 176 | $(event.target).children('.ui-selecting').not(':first').removeClass('ui-selecting'); 177 | $(ui.selecting).removeClass("ui-selecting"); 178 | } else { 179 | animateHelper(ui.selecting.id); 180 | last_selectedItem_id = ui.selecting.id; 181 | if (firstRun) { 182 | firstRun = false; 183 | } 184 | } 185 | } 186 | }); 187 | }); 188 | 189 | function mouseEventHandler(e) {"use strict"; 190 | 191 | var mouseMove, mouseUp, prevMouse; 192 | 193 | prevMouse = { 194 | x : e.clientX, 195 | y : e.clientY 196 | }; 197 | 198 | mouseMove = function(e) { 199 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 200 | 201 | unitsPerPixel = 1 / map.getScale(); 202 | lon0 = map.getCentralLongitude(); 203 | lon0 -= dx * unitsPerPixel; 204 | map.setCentralLongitude(lon0); 205 | 206 | prevMouse.x = e.clientX; 207 | prevMouse.y = e.clientY; 208 | e.preventDefault(); 209 | }; 210 | 211 | mouseUp = function(e) { 212 | document.body.style.cursor = null; 213 | document.removeEventListener('mousemove', mouseMove, false); 214 | document.removeEventListener('mouseup', mouseUp, false); 215 | }; 216 | 217 | document.body.style.cursor = 'move'; 218 | document.addEventListener('mousemove', mouseMove, false); 219 | document.addEventListener('mouseup', mouseUp, false); 220 | } 221 | 222 | function initBoundingParallelSlider() {"use strict"; 223 | 224 | function action(event, ui) { 225 | //if animation is going on, interrupt it! 226 | clearInterval(animationIntervalId); 227 | animateFlag = false; 228 | 229 | // fix a bug in jQuery slider 230 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 231 | $("#sliderBoundingParallel").slider('value', ui.value); 232 | updateSliderTexts(); 233 | updateProjection(); 234 | } 235 | 236 | // create the slider 237 | $("#sliderBoundingParallel").slider({ 238 | orientation : "horizontal", 239 | range : "min", 240 | min : 0, 241 | max : 90, 242 | value : 90, 243 | step : 1, 244 | slide : action 245 | }); 246 | } 247 | 248 | function initBoundingMeridianSlider() {"use strict"; 249 | function action(event, ui) { 250 | //if animation is going on, interrupt it! 251 | clearInterval(animationIntervalId); 252 | animateFlag = false; 253 | 254 | // fix a bug in jQuery slider 255 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 256 | $("#sliderBoundingMeridian").slider('value', ui.value); 257 | updateSliderTexts(); 258 | updateProjection(); 259 | } 260 | 261 | // create the slider 262 | $("#sliderBoundingMeridian").slider({ 263 | orientation : "horizontal", 264 | range : "min", 265 | min : 0, 266 | max : 180, 267 | value : 180, 268 | step : 1, 269 | slide : action 270 | }); 271 | } 272 | 273 | function initEquatorSlider() {"use strict"; 274 | function action(event, ui) { 275 | //if animation is going on, interrupt it! 276 | clearInterval(animationIntervalId); 277 | animateFlag = false; 278 | 279 | // fix a bug in jQuery slider 280 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 281 | $("#sliderEquator").slider('value', ui.value); 282 | updateSliderTexts(); 283 | updateProjection(); 284 | } 285 | 286 | // create the slider 287 | $("#sliderEquator").slider({ 288 | orientation : "horizontal", 289 | range : "min", 290 | min : 1, 291 | max : 3.5, 292 | value : 1.41, 293 | step : 0.01, 294 | slide : action 295 | }); 296 | } 297 | 298 | function getMapScale(map) {"use strict"; 299 | 300 | // a reduction factor such that the Lambert Cylindrical fits horizontally 301 | var K = 0.62, 302 | // use the Lambert azimuthal as a reference to compute the map scale 303 | // otherwise different scales would result depending on the chosen parameters, which would 304 | // result in abrupt changes when the canvas is resized. 305 | // the graticule diameter of the Lambert azimuthal is 4 306 | hScale = map.getCanvas().width / 4, vScale = map.getCanvas().height / 4; 307 | return Math.min(vScale, hScale) * K; 308 | } 309 | 310 | 311 | $(window).load(function() {"use strict"; 312 | // currently styling works like this: 313 | // - if there's a fillStyle then it will be filled 314 | // - if there's a strokeStyle then it will be stroked 315 | // - points are always 3px rectangles 316 | // - polylines can't be filled 317 | 318 | var layers, graticule; 319 | 320 | function Style(strokeStyle, lineWidth, fillStyle) { 321 | if (strokeStyle !== undefined) { 322 | this.strokeStyle = strokeStyle; 323 | this.lineWidth = lineWidth; 324 | } 325 | if (fillStyle !== undefined) { 326 | this.fillStyle = fillStyle; 327 | } 328 | } 329 | 330 | //create the map 331 | graticule = new Graticule(new Style("#77b", "1"), 15); 332 | layers = []; 333 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 334 | layers.push(graticule); 335 | 336 | map = createMap(layers, new TransformableLambert(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 337 | 338 | $(window).resize(function() { 339 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 340 | map.setScale(getMapScale(map)); 341 | }); 342 | 343 | $("#map").bind("mousedown", mouseEventHandler); 344 | 345 | initBoundingParallelSlider(); 346 | initBoundingMeridianSlider(); 347 | initEquatorSlider(); 348 | updateSliderTexts(); 349 | updateProjection(); 350 | map.setScale(getMapScale(map)); 351 | }); 352 | -------------------------------------------------------------------------------- /LambertTransition/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wagner's Transformation for the Lambert Azimuthal Equal-Area Projection 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Wagner's Transformation for the Lambert Azimuthal Equal-Area Projection

24 | 25 |
26 | 27 |
28 |
29 | Bounding Parallel 30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 | Bounding Meridian 39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 | Equator / Central Meridian Ratio 48 |
49 | 50 |
51 |
52 |
53 |
54 |
55 | 56 |
    57 |
  1. 58 | Lambert Azimuthal 59 |
  2. 60 |
  3. 61 | Hammer 62 |
  4. 63 |
  5. 64 | Eckert-Greifendorff 65 |
  6. 66 |
  7. 67 | Quartic Authalic 68 |
  8. 69 |
  9. 70 | Wagner VII 71 |
  10. 72 |
  11. 73 | Lambert Cylindrical 74 |
  12. 75 |
  13. 76 | Pseudocylindrical 77 |
  14. 78 |
79 | 80 |
81 | 82 |
83 | 84 |
85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /LambertTransition/projection-selection-list.css: -------------------------------------------------------------------------------- 1 | #StParallel { 2 | margin-bottom: 30px; 3 | } 4 | 5 | #selectable { 6 | list-style-type: none; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | #feedback { 11 | font-size: 1.4em; 12 | } 13 | #selectable .ui-selecting { 14 | background: #BBBBBB; 15 | } 16 | #selectable .ui-selected { 17 | background: #BBBBBB; 18 | color: white; 19 | } 20 | #selectable li { 21 | margin-top: 3px; 22 | margin-bottom: 3px; 23 | padding: 0.4em; 24 | width: 195px; 25 | height: 18px; 26 | } -------------------------------------------------------------------------------- /MillerTransformation/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, MillerTransformation, createMap*/ 2 | 3 | var map; 4 | 5 | function getMapScale(map) {"use strict"; 6 | var K = 0.90; 7 | return Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()) * K; 8 | } 9 | 10 | function mouseEventHandler(e) {"use strict"; 11 | 12 | var mouseMove, mouseUp, prevMouse; 13 | 14 | prevMouse = { 15 | x : e.clientX, 16 | y : e.clientY 17 | }; 18 | 19 | mouseMove = function(e) { 20 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 21 | 22 | unitsPerPixel = 1 / getMapScale(map); 23 | lon0 = map.getCentralLongitude(); 24 | lon0 -= dx * unitsPerPixel; 25 | map.setCentralLongitude(lon0); 26 | prevMouse.x = e.clientX; 27 | prevMouse.y = e.clientY; 28 | e.preventDefault(); 29 | }; 30 | 31 | mouseUp = function(e) { 32 | document.body.style.cursor = null; 33 | document.removeEventListener('mousemove', mouseMove, false); 34 | document.removeEventListener('mouseup', mouseUp, false); 35 | }; 36 | 37 | document.body.style.cursor = 'move'; 38 | document.addEventListener('mousemove', mouseMove, false); 39 | document.addEventListener('mouseup', mouseUp, false); 40 | } 41 | 42 | function initSliders() {"use strict"; 43 | 44 | // M slider 45 | $(function() { 46 | 47 | function action(event, ui) { 48 | // fix a bug in jQuery slider 49 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 50 | $("#mSlider").slider('value', ui.value); 51 | $("#mText").text("m: " + ui.value); 52 | $("#aspectText").text("Width:Height 1:" + (map.getGraticuleHeight() / map.getGraticuleWidth()).toFixed(3)); 53 | map.getProjection().setM(ui.value); 54 | map.render(); 55 | } 56 | 57 | // create the meridian slider 58 | $("#mSlider").slider({ 59 | orientation : "horizontal", 60 | range : "min", 61 | min : 0.5, 62 | max : 5, 63 | value : 1.5, 64 | step : 0.01, 65 | slide : action 66 | }); 67 | $("#mText").text("m: " + 1.5); 68 | }); 69 | 70 | // N slider 71 | $(function() { 72 | 73 | function action(event, ui) { 74 | // fix a bug in jQuery slider 75 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 76 | $("#nSlider").slider('value', ui.value); 77 | $("#nText").text("n: " + ui.value); 78 | $("#aspectText").text("Width:Height 1:" + (map.getGraticuleHeight() / map.getGraticuleWidth()).toFixed(3)); 79 | map.getProjection().setN(ui.value); 80 | map.render(); 81 | } 82 | 83 | // create the meridian slider 84 | $("#nSlider").slider({ 85 | orientation : "horizontal", 86 | range : "min", 87 | min : 1, 88 | max : 5, 89 | value : 1.5, 90 | step : 0.01, 91 | slide : action 92 | }); 93 | $("#nText").text("n: " + 1.5); 94 | }); 95 | } 96 | 97 | 98 | $(window).load(function() {"use strict"; 99 | 100 | // currently styling works like this: 101 | // - if there's a fillStyle then it will be filled 102 | // - if there's a strokeStyle then it will be stroked 103 | // - points are always 3px rectangles 104 | // - polylines can't be filled 105 | 106 | var layers = []; 107 | 108 | function Style(strokeStyle, lineWidth, fillStyle) { 109 | if (strokeStyle !== undefined) { 110 | this.strokeStyle = strokeStyle; 111 | this.lineWidth = lineWidth; 112 | } 113 | if (fillStyle !== undefined) { 114 | this.fillStyle = fillStyle; 115 | } 116 | } 117 | 118 | //create the map 119 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 120 | layers.push(new Graticule(new Style("#77b", "1"), 15)); 121 | 122 | map = createMap(layers, new MillerTransformation(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 123 | $(window).resize(function() { 124 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 125 | map.setScale(getMapScale(map)); 126 | }); 127 | 128 | map.setScale(getMapScale(map)); 129 | 130 | // init UI controls 131 | $("#map").bind("mousedown", mouseEventHandler); 132 | 133 | initSliders(); 134 | 135 | // ie 136 | $("#mapCanvas")[0].onselectstart = function() { 137 | return false; 138 | }; 139 | // mozilla 140 | $("#mapCanvas")[0].onmousedown = function() { 141 | return false; 142 | }; 143 | 144 | }); 145 | 146 | -------------------------------------------------------------------------------- /MillerTransformation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapCanvas Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Miller's Transformation for Compromise Cylindrical Projections

17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /MillerTransformation/static.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eee; 3 | font: 12px sans-serif; 4 | } 5 | 6 | .map { 7 | height:500px; 8 | width:100%; 9 | padding:0px; 10 | border:0px; 11 | background-color: #fff; 12 | position:relative; 13 | } 14 | 15 | .mapCanvas { 16 | width: 100%; 17 | height: 100%; 18 | position:absolute; 19 | } 20 | 21 | #meridianContainer { 22 | position: relative; 23 | margin-left:auto; 24 | margin-right:auto; 25 | width:380px; 26 | } 27 | 28 | #meridianSlider{ 29 | position: relative; 30 | width:300px; 31 | float: left; 32 | } 33 | 34 | #meridianText{ 35 | position: relative; 36 | width: 70px; 37 | margin-left: 5px; 38 | float: left; 39 | } 40 | 41 | #mSlider { 42 | float: left; 43 | width: 400px; 44 | } 45 | 46 | #nSlider { 47 | float: left; 48 | width: 400px; 49 | } -------------------------------------------------------------------------------- /ProjectionList/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, AspectAdaptiveCylindrical, CylindricalStereographic, EqualAreaCylindrical, Equirectangular, createMap*/ 2 | 3 | var map; 4 | 5 | function getProjection(projectionName) { 6 | "use strict"; 7 | var proj; 8 | 9 | switch (projectionName) { 10 | case "ArdenClose": 11 | return new ArdenClose(); 12 | case "Bonne": 13 | return new Bonne(); 14 | case "BraunStereographic": 15 | proj = new CylindricalStereographic(); 16 | proj.setStandardParallel(0); 17 | return proj; 18 | case "Braun2": 19 | return new Braun2(); 20 | case "BSAM": 21 | proj = new CylindricalStereographic(); 22 | proj.setStandardParallel(30 / 180 * Math.PI); 23 | return proj; 24 | case "Canters1": 25 | return new Canters1(); 26 | case "Canters2": 27 | return new Canters2(); 28 | case "CentralCylindrical": 29 | return new CentralCylindrical(); 30 | case "CylindricalStereographic": 31 | return new CylindricalStereographic(); 32 | case "Eckert4": 33 | return new Eckert4(); 34 | case "Eckert6": 35 | return new Eckert6(); 36 | case "EqualAreaCylindrical": 37 | return new EqualAreaCylindrical(); 38 | case "Equirectangular": 39 | return new Equirectangular(); 40 | case "GallIsographic": 41 | proj = new Equirectangular(); 42 | proj.setStandardParallel(Math.PI / 4); 43 | return proj; 44 | case "GallPeters": 45 | proj = new EqualAreaCylindrical(); 46 | proj.setStandardParallel(Math.PI / 4); 47 | return proj; 48 | case "GallStereographic": 49 | proj = new CylindricalStereographic(); 50 | proj.setStandardParallel(Math.PI / 4); 51 | return proj; 52 | case "Hammer": 53 | return new Hammer(); 54 | case "Hufnagel": 55 | return new Hufnagel(); 56 | case "Kamenetskiy1": 57 | proj = new CylindricalStereographic(); 58 | proj.setStandardParallel(55 / 180 * Math.PI); 59 | return proj; 60 | case "Kavrayskiy1": 61 | return new Kavrayskiy1(); 62 | case "Kavrayskiy5": 63 | return new Kavrayskiy5(); 64 | case "KharchenkoShabanova": 65 | return new KharchenkoShabanova(); 66 | case "LambertEqualAreaCylindrical": 67 | return new LambertEqualAreaCylindrical(); 68 | case "McBrydeThomas1": 69 | return new McBrydeThomas1(); 70 | case "McBrydeThomas2": 71 | return new McBrydeThomas2(); 72 | case "Mercator": 73 | return new Mercator(); 74 | case "Miller": 75 | return new Miller(); 76 | case "Miller2": 77 | return new Miller2(); 78 | case "MillerGall": 79 | proj = new CylindricalStereographic(); 80 | // standard parallel at 66.16 degrees 81 | proj.setStandardParallel(2 / Math.sqrt(3)); 82 | return proj; 83 | case "MillerPerspective": 84 | return new MillerPerspective(); 85 | case "Mollweide": 86 | return new Mollweide(); 87 | case "NaturalEarth": 88 | return new NaturalEarth(); 89 | case "Pavlov": 90 | return new Pavlov(); 91 | case "PlateCarree": 92 | proj = new Equirectangular(); 93 | proj.setStandardParallel(0); 94 | return proj; 95 | case "PutninsP4P": 96 | return new PutninsP4P(); 97 | case "RMillerMinOverallScaleDistortion": 98 | proj = new Equirectangular(); 99 | proj.setStandardParallel(37.5 / 180 * Math.PI); 100 | return proj; 101 | case "RMillerMinContinentalScaleDistortion": 102 | proj = new Equirectangular(); 103 | proj.setStandardParallel(43.5 / 180 * Math.PI); 104 | return proj; 105 | case "Robinson": 106 | return new Robinson(); 107 | case "Sinusoidal": 108 | return new Sinusoidal(); 109 | case "SineSeries": 110 | return new SineSeries(); 111 | case "Strebe1995": 112 | return new Strebe1995(); 113 | case "Tobler1": 114 | return new Tobler1(); 115 | case "Tobler2": 116 | return new Tobler2(); 117 | case "Urmayev2": 118 | return new Urmayev2(); 119 | case "Urmayev3": 120 | return new Urmayev3(); 121 | case "Wagner1": 122 | return new Wagner1(); 123 | case "Wagner4": 124 | return new Wagner4(); 125 | case "Wagner7": 126 | return new Wagner7(); 127 | case "WagnerPseudocylindrical": 128 | return new WagnerPseudocylindrical(); 129 | case "LambertAzimuthalEqualAreaPolar": 130 | return new LambertAzimuthalEqualAreaPolar(); 131 | default: 132 | return null; 133 | } 134 | } 135 | 136 | function isAdaptiveProjectionSelected() { 137 | "use strict"; 138 | var selectedOption = $('#projection-menu').find(":selected").text(); 139 | return selectedOption.indexOf("Adaptive") !== -1; 140 | } 141 | 142 | function getMapScale(map) { 143 | "use strict"; 144 | var K = 0.97; 145 | return Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()) * K; 146 | } 147 | 148 | function setStandardParallels(projection, lat0) { 149 | "use strict"; 150 | if (typeof projection.setStandardParallel !== 'undefined') { 151 | projection.setStandardParallel(lat0); 152 | } 153 | map.setScale(getMapScale(map)); 154 | } 155 | 156 | function writeSliderValue(nbr) { 157 | "use strict"; 158 | $("#sliderValueText").text('\xB1' + nbr.toFixed(1) + "\u00B0"); 159 | } 160 | 161 | function mouseEventHandler(e) { 162 | "use strict"; 163 | 164 | var mouseMove, mouseUp, prevMouse; 165 | 166 | prevMouse = { 167 | x: e.clientX, 168 | y: e.clientY 169 | }; 170 | 171 | mouseMove = function (e) { 172 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 173 | 174 | unitsPerPixel = 1 / map.getScale(); 175 | lon0 = map.getCentralLongitude(); 176 | lon0 -= dx * unitsPerPixel; 177 | map.setCentralLongitude(lon0); 178 | 179 | prevMouse.x = e.clientX; 180 | prevMouse.y = e.clientY; 181 | e.preventDefault(); 182 | }; 183 | 184 | mouseUp = function (e) { 185 | document.body.style.cursor = null; 186 | document.removeEventListener('mousemove', mouseMove, false); 187 | document.removeEventListener('mouseup', mouseUp, false); 188 | }; 189 | 190 | document.body.style.cursor = 'move'; 191 | document.addEventListener('mousemove', mouseMove, false); 192 | document.addEventListener('mouseup', mouseUp, false); 193 | } 194 | 195 | function updateSliderEnabledState() { 196 | "use strict"; 197 | var enabled = false; 198 | 199 | if (map.isDrawn() && isAdaptiveProjectionSelected()) { 200 | enabled = true; 201 | writeSliderValue($("#slider").slider("value")); 202 | } else { 203 | $('#sliderValueText').text("-"); 204 | } 205 | $('#slider').slider(enabled ? 'enable' : 'disable'); 206 | } 207 | 208 | function setMapProjection() { 209 | "use strict"; 210 | map.setProjection(getProjection($("#projection-menu").val())); 211 | map.setScale(getMapScale(map)); 212 | updateSliderEnabledState(); 213 | if (isAdaptiveProjectionSelected()) { 214 | setStandardParallels(map.getProjection(), $("#slider").slider("value") / 100); 215 | } 216 | } 217 | 218 | function initSlider() { 219 | "use strict"; 220 | 221 | $(function () { 222 | 223 | function action(event, ui) { 224 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 225 | $("#slider").slider('value', ui.value); 226 | writeSliderValue(ui.value); 227 | setStandardParallels(map.getProjection(), ui.value / 100); 228 | map.render(); 229 | } 230 | 231 | // create the slider 232 | $("#slider").slider({ 233 | orientation: "horizontal", 234 | range: "min", 235 | min: 0, 236 | max: 90, 237 | value: 57, 238 | step: 1, 239 | slide: action 240 | }); 241 | var sliderValue = $("#slider").slider("value"); 242 | setStandardParallels(map.getProjection(), sliderValue); 243 | updateSliderEnabledState(); 244 | }); 245 | } 246 | 247 | 248 | $(window).load(function () { 249 | "use strict"; 250 | 251 | // currently styling works like this: 252 | // - if there's a fillStyle then it will be filled 253 | // - if there's a strokeStyle then it will be stroked 254 | // - points are always 3px rectangles 255 | // - polylines can't be filled 256 | 257 | var layers, projection, graticule; 258 | 259 | function Style(strokeStyle, lineWidth, fillStyle) { 260 | if (strokeStyle !== undefined) { 261 | this.strokeStyle = strokeStyle; 262 | this.lineWidth = lineWidth; 263 | } 264 | if (fillStyle !== undefined) { 265 | this.fillStyle = fillStyle; 266 | } 267 | } 268 | 269 | //create the map 270 | graticule = new Graticule(new Style("#77b", "1"), 15); 271 | layers = []; 272 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 273 | //layers.push(new Layer("../data/cities/cities_2", new Style(undefined, undefined, "#ff0000"))); 274 | layers.push(graticule); 275 | projection = getProjection($("#projection-menu").val()); 276 | map = createMap(layers, projection, $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 277 | 278 | $(window).resize(function () { 279 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 280 | map.setScale(getMapScale(map)); 281 | }); 282 | 283 | // init UI controls 284 | $("#map").bind("mousedown", mouseEventHandler); 285 | $("#projection-menu").bind("change", setMapProjection); 286 | initSlider(); 287 | updateSliderEnabledState(); 288 | }); -------------------------------------------------------------------------------- /ProjectionList/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapCanvas Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

A Compilation of Projections Included in CanvasMap

18 | 19 |
20 | 21 |
22 | 74 | 75 |
76 | Standard Parallels 77 |
78 | 79 |
80 |
81 |
82 | - 83 |
84 |
85 | 86 |
87 | 88 |
89 | 90 |
91 | 92 |
93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CanvasMap 2 | ========= 3 | 4 | CanvasMap is a simple HTML framework to experiment with projection equations. 5 | CanvasMap uses JavaScript, HTML5 Canvas, and JQuery. 6 | 7 | Sample: http://cartography.oregonstate.edu/ScaleAdaptiveWebMapProjections.html#CanvasMapExample 8 | 9 | Programming by Bernhard Jenny and Bojan Savric, College of Earth, Ocean and 10 | Atmospheric Sciences, Oregon State University, and Mostafa El-Fouly, TU Munich. 11 | 12 | Contact: Bernhard Jenny 13 | 14 | License: GNU General Public License Version 2: http://www.gnu.org/licenses/gpl-2.0.html 15 | 16 | ##File structure 17 | CanvasMap contains the core JavaScript files. Code related to the map and projections is 18 | in src/. CanvasMap.js is created by the build.xml ant script. Include this file in the 19 | HTML file. 20 | 21 | ## Demos 22 | All code specific to an application is included in the UI.js files. These files construct 23 | the HTML GUI, provide event handlers and create the map with a projection. 24 | 25 | SimpleMap shows a map with a static projection. 26 | CentralMeridianSlider shows a map and slider to adjust the central meridian. 27 | ProjectionList has a menu to select from various projections. 28 | LambertTransitionSlider illustrates Wagner's transformation applied to the Lambert 29 | azimuthal projection to create a variety of equal-area projections. 30 | MillerTransformation illustrates a transformation of the Mercator projection suggested 31 | by Miller in 1942. 32 | StrebeTransformation illustrates a transformation method invented by D. Strebe. 33 | 34 | ## Programming Model 35 | UI.js: 36 | DOM event handlers and construction of the model consisting of a map, one or more layers 37 | and a projection. 38 | 39 | layer.js: 40 | A layer loads a shapefile (points, polylines or polygons), and projects and draws the data. 41 | Polygons are not filled. Shapefiles must use 'geographical' coordinates. 42 | 43 | map.js: 44 | Contains an array of layers, a projection, and scale factor applied before drawing the map. 45 | After the layer, canvas, projection and scale are created, invoke map.load to load the 46 | layer geometry. 47 | 48 | ## Extending CanvasMap with Custom Projections 49 | To extend CanvasMap with an additional projection, duplicate an existing 50 | projection file in the projections folder and change the function name and the included 51 | toString() and forward() functions. 52 | 53 | The forward function receives longitude and latitude values in radians. The projected 54 | coordinates have to be written to the xy array. The xy parameter is only for returning the 55 | projected coordinates and does not contain valid data when the function is called. 56 | 57 | Important: use Apache Ant to concatenate and minify all JavaScript files in the src folder. 58 | The default Ant target creates CanvasMap.js and CanvasMap-min.js. To run Ant, 59 | cd to CanvasMap, then type ant. 60 | 61 | To apply the new projection to the map, change UI.js. 62 | If you use the "ProjectionList" map, add your projection to the getProjection function in 63 | UI.js. Also include your projection in the element has to be unique and match the projectionName 65 | parameter of getProjection in UI.js. 66 | -------------------------------------------------------------------------------- /SimpleMap/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, NaturalEarth, createMap*/ 2 | 3 | var map; 4 | 5 | 6 | function getMapScale(map) {"use strict"; 7 | var K = 0.90; 8 | return Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()) * K; 9 | } 10 | 11 | function mouseEventHandler(e) {"use strict"; 12 | 13 | var mouseMove, mouseUp, prevMouse; 14 | 15 | prevMouse = { 16 | x : e.clientX, 17 | y : e.clientY 18 | }; 19 | 20 | mouseMove = function(e) { 21 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 22 | 23 | unitsPerPixel = 1 / map.getScale(); 24 | lon0 = map.getCentralLongitude(); 25 | lon0 -= dx * unitsPerPixel; 26 | map.setCentralLongitude(lon0); 27 | 28 | prevMouse.x = e.clientX; 29 | prevMouse.y = e.clientY; 30 | e.preventDefault(); 31 | }; 32 | 33 | mouseUp = function(e) { 34 | document.body.style.cursor = null; 35 | document.removeEventListener('mousemove', mouseMove, false); 36 | document.removeEventListener('mouseup', mouseUp, false); 37 | }; 38 | 39 | document.body.style.cursor = 'move'; 40 | document.addEventListener('mousemove', mouseMove, false); 41 | document.addEventListener('mouseup', mouseUp, false); 42 | } 43 | 44 | $(window).load(function() {"use strict"; 45 | 46 | // currently styling works like this: 47 | // - if there's a fillStyle then it will be filled 48 | // - if there's a strokeStyle then it will be stroked 49 | // - points are always 3px rectangles 50 | // - polylines can't be filled 51 | 52 | var layers = []; 53 | 54 | function Style(strokeStyle, lineWidth, fillStyle) { 55 | if (strokeStyle !== undefined) { 56 | this.strokeStyle = strokeStyle; 57 | this.lineWidth = lineWidth; 58 | } 59 | if (fillStyle !== undefined) { 60 | this.fillStyle = fillStyle; 61 | } 62 | } 63 | 64 | //create the map 65 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 66 | layers.push(new Graticule(new Style("#77b", "1"), 15)); 67 | 68 | map = createMap(layers, new NaturalEarth(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 69 | $(window).resize(function() { 70 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 71 | map.setScale(getMapScale(map)); 72 | }); 73 | 74 | map.setScale(getMapScale(map)); 75 | 76 | // init UI controls 77 | $("#map").bind("mousedown", mouseEventHandler); 78 | }); -------------------------------------------------------------------------------- /SimpleMap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapCanvas Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

CanvasMap: Simple Map Demo

17 |
Click and drag to adjust the central meridian
18 |
19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SineSeries/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, SineSeries, createMap*/ 2 | 3 | var map; 4 | 5 | function getMapScale(map) { 6 | "use strict"; 7 | //return 300; 8 | var K = 0.9, s = Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()); 9 | if (isNaN(s) || s > map.getCanvas().width || s < 5) { 10 | s = 0.5 * map.getCanvas().width / (2 * Math.PI); 11 | } 12 | return s * K; 13 | } 14 | 15 | function writeSliderValues(p, q) { 16 | "use strict"; 17 | $("#pSliderText").text(p); 18 | $("#qSliderText").text(q); 19 | } 20 | 21 | function mouseEventHandler(e) { 22 | "use strict"; 23 | 24 | var mouseMove, mouseUp, prevMouse; 25 | 26 | prevMouse = { 27 | x: e.clientX, 28 | y: e.clientY 29 | }; 30 | 31 | mouseMove = function (e) { 32 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 33 | 34 | unitsPerPixel = 1 / getMapScale(map); 35 | lon0 = map.getCentralLongitude(); 36 | lon0 -= dx * unitsPerPixel; 37 | 38 | map.setCentralLongitude(lon0); 39 | 40 | prevMouse.x = e.clientX; 41 | prevMouse.y = e.clientY; 42 | e.preventDefault(); 43 | }; 44 | 45 | mouseUp = function (e) { 46 | document.body.style.cursor = null; 47 | document.removeEventListener('mousemove', mouseMove, false); 48 | document.removeEventListener('mouseup', mouseUp, false); 49 | }; 50 | 51 | document.body.style.cursor = 'move'; 52 | document.addEventListener('mousemove', mouseMove, false); 53 | document.addEventListener('mouseup', mouseUp, false); 54 | } 55 | 56 | function initSliders(p, q) { 57 | "use strict"; 58 | 59 | $(function () { 60 | 61 | function sliderAction() { 62 | var sp = $("#pSlider").slider("option", "value"), 63 | sq = $("#qSlider").slider("option", "value"); 64 | writeSliderValues(sp, sq); 65 | map.getProjection().setP(sp); 66 | map.getProjection().setQ(sq); 67 | adjustMapSize(); 68 | map.render(); 69 | } 70 | 71 | function pAction(event, ui) { 72 | // fix a bug in jQuery slider 73 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 74 | $("#pSlider").slider('value', ui.value); 75 | sliderAction(); 76 | } 77 | 78 | function qAction(event, ui) { 79 | // fix a bug in jQuery slider 80 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 81 | $("#qSlider").slider('value', ui.value); 82 | sliderAction(); 83 | } 84 | 85 | // create the sliders 86 | $("#pSlider").slider({ 87 | orientation: "horizontal", 88 | range: "min", 89 | min: 1, 90 | max: 3, 91 | value: p, 92 | step: 0.01, 93 | slide: pAction 94 | }); 95 | 96 | $("#qSlider").slider({ 97 | orientation: "horizontal", 98 | range: "min", 99 | min: 1, 100 | max: 3, 101 | value: q, 102 | step: 0.005, 103 | slide: qAction 104 | }); 105 | 106 | writeSliderValues(p, q); 107 | map.getProjection().setP(p); 108 | map.getProjection().setQ(q); 109 | }); 110 | } 111 | 112 | function adjustMapSize() { 113 | var mapHeight = $(window).outerHeight(true) - $("#guiControlsContainer").outerHeight(true) - $("#header").outerHeight(true); 114 | $("#map").height(mapHeight); 115 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 116 | map.setScale(getMapScale(map)); 117 | } 118 | 119 | function setPQ(p, q) { 120 | map.getProjection().setP(p); 121 | map.getProjection().setQ(q); 122 | $("#pSlider").slider('value', p); 123 | $("#qSlider").slider('value', q); 124 | writeSliderValues(p, q); 125 | adjustMapSize(); 126 | map.render(); 127 | } 128 | 129 | $(window).load(function () { 130 | "use strict"; 131 | 132 | var p = 1.33, 133 | q = 1.135; 134 | 135 | // currently styling works like this: 136 | // - if there's a fillStyle then it will be filled 137 | // - if there's a strokeStyle then it will be stroked 138 | // - points are always 3px rectangles 139 | // - polylines can't be filled 140 | 141 | var layers = []; 142 | 143 | function Style(strokeStyle, lineWidth, fillStyle) { 144 | if (strokeStyle !== undefined) { 145 | this.strokeStyle = strokeStyle; 146 | this.lineWidth = lineWidth; 147 | } 148 | if (fillStyle !== undefined) { 149 | this.fillStyle = fillStyle; 150 | } 151 | } 152 | 153 | //create the map 154 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 155 | layers.push(new Graticule(new Style("#77b", "1"), 15)); 156 | 157 | map = createMap(layers, new SineSeries(p, q), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 158 | $(window).resize(function () { 159 | adjustMapSize(); 160 | }); 161 | 162 | map.setScale(getMapScale(map)); 163 | 164 | // init UI controls 165 | $("#map").bind("mousedown", mouseEventHandler); 166 | 167 | initSliders(p, q); 168 | 169 | // ie 170 | $("#mapCanvas")[0].onselectstart = function () { 171 | return false; 172 | }; 173 | // mozilla 174 | $("#mapCanvas")[0].onmousedown = function () { 175 | return false; 176 | }; 177 | 178 | adjustMapSize(); 179 | }); 180 | 181 | -------------------------------------------------------------------------------- /SineSeries/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sine Series 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 20 | 21 |
22 | 23 |
24 | 25 |
26 |
27 |
p
28 |
29 |
30 |
31 |
32 | 33 |
34 |
q
35 |
36 |
37 |
38 | 39 |
40 | 41 | 42 | 43 |
44 |
45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /SineSeries/static.css: -------------------------------------------------------------------------------- 1 | .map { 2 | height: 900px; 3 | width: 100%; 4 | padding: 0px; 5 | border: 0px; 6 | margin: 0px; 7 | background-color: #fff; 8 | position: relative; 9 | } 10 | 11 | body { 12 | background-color: #eee; 13 | font-family: "Lucida Grande", "Open Sans", sans-serif; 14 | font-size: 12px; 15 | padding: 0px; 16 | border: 0px; 17 | margin: 0px; 18 | } 19 | 20 | h1 { 21 | font-family: "Lucida Grande", "Open Sans", sans-serif; 22 | font-size: 14px; 23 | font-weight: bolder; 24 | text-align: center; 25 | } 26 | 27 | .guiTitle{ 28 | font-family: "Lucida Grande", "Open Sans", sans-serif; 29 | font-size: 12px; 30 | font-weight: bolder; 31 | margin-bottom: 5px; 32 | } 33 | 34 | .guiGroup { 35 | padding-bottom: 15px; 36 | } 37 | 38 | .mapCanvas { 39 | width: 100%; 40 | height: 100%; 41 | position:absolute; 42 | } 43 | 44 | #guiControlsContainer { 45 | position: relative; 46 | margin-left:auto; 47 | margin-right:auto; 48 | width:380px; 49 | margin-top: 20px; 50 | } 51 | 52 | .slider{ 53 | position: relative; 54 | width:300px; 55 | float: left; 56 | } 57 | 58 | 59 | .sliderText{ 60 | position: relative; 61 | width: 70px; 62 | margin-left: 5px; 63 | float: left; 64 | } 65 | 66 | #buttonGroup { 67 | margin-top: 15px; 68 | } 69 | -------------------------------------------------------------------------------- /StrebeTransformation/UI.js: -------------------------------------------------------------------------------- 1 | /*globals Map, Layer, Graticule, $, Strebe1995, createMap*/ 2 | 3 | var map; 4 | 5 | function getMapScale(map) {"use strict"; 6 | return 80; 7 | //var K = 0.80; 8 | //return Math.min(map.getMaximumVerticalScale(), map.getMaximumHorizontalScale()) * K; 9 | } 10 | 11 | function writeSliderValue(nbr) {"use strict"; 12 | $("#sliderText").text(nbr); 13 | } 14 | 15 | function mouseEventHandler(e) {"use strict"; 16 | 17 | var mouseMove, mouseUp, prevMouse; 18 | 19 | prevMouse = { 20 | x : e.clientX, 21 | y : e.clientY 22 | }; 23 | 24 | mouseMove = function(e) { 25 | var unitsPerPixel, lon0, dx = e.clientX - prevMouse.x; 26 | 27 | unitsPerPixel = 1 / getMapScale(map); 28 | lon0 = map.getCentralLongitude(); 29 | lon0 -= dx * unitsPerPixel; 30 | 31 | map.setCentralLongitude(lon0); 32 | 33 | prevMouse.x = e.clientX; 34 | prevMouse.y = e.clientY; 35 | e.preventDefault(); 36 | }; 37 | 38 | mouseUp = function(e) { 39 | document.body.style.cursor = null; 40 | document.removeEventListener('mousemove', mouseMove, false); 41 | document.removeEventListener('mouseup', mouseUp, false); 42 | }; 43 | 44 | document.body.style.cursor = 'move'; 45 | document.addEventListener('mousemove', mouseMove, false); 46 | document.addEventListener('mouseup', mouseUp, false); 47 | } 48 | 49 | function initSlider() {"use strict"; 50 | 51 | $(function() { 52 | 53 | function action(event, ui) { 54 | // fix a bug in jQuery slider 55 | // http://stackoverflow.com/questions/9121160/jquery-ui-slider-value-returned-from-slide-event-on-release-is-different-fro 56 | $("#slider").slider('value', ui.value); 57 | writeSliderValue(ui.value); 58 | map.getProjection().setScaleFactor(ui.value); 59 | map.render(); 60 | } 61 | 62 | // create the slider 63 | $("#slider").slider({ 64 | orientation : "horizontal", 65 | range : "min", 66 | min : 0, 67 | max : 5, 68 | value : 1.35, 69 | step : 0.01, 70 | slide : action 71 | }); 72 | writeSliderValue(1.35); 73 | }); 74 | } 75 | 76 | function getProjection(projectionName) {"use strict"; 77 | var proj; 78 | 79 | switch (projectionName) { 80 | case "AlbersConic": 81 | return new AlbersConic(); 82 | case "ArdenClose": 83 | return new ArdenClose(); 84 | case "Bonne": 85 | return new Bonne(); 86 | case "BraunStereographic": 87 | proj = new CylindricalStereographic(); 88 | proj.setStandardParallel(0); 89 | return proj; 90 | case "Braun2": 91 | return new Braun2(); 92 | case "BSAM": 93 | proj = new CylindricalStereographic(); 94 | proj.setStandardParallel(30 / 180 * Math.PI); 95 | return proj; 96 | case "Canters1": 97 | return new Canters1(); 98 | case "Canters2": 99 | return new Canters2(); 100 | case "CentralCylindrical": 101 | return new CentralCylindrical(); 102 | case "CylindricalStereographic": 103 | return new CylindricalStereographic(); 104 | case "Eckert4": 105 | return new Eckert4(); 106 | case "EqualAreaCylindrical": 107 | return new EqualAreaCylindrical(); 108 | case "Equirectangular": 109 | return new Equirectangular(); 110 | case "GallIsographic": 111 | proj = new Equirectangular(); 112 | proj.setStandardParallel(Math.PI / 4); 113 | return proj; 114 | case "GallPeters": 115 | proj = new EqualAreaCylindrical(); 116 | proj.setStandardParallel(Math.PI / 4); 117 | return proj; 118 | case "GallStereographic": 119 | proj = new CylindricalStereographic(); 120 | proj.setStandardParallel(Math.PI / 4); 121 | return proj; 122 | case "Hammer": 123 | return new Hammer(); 124 | case "Hufnagel": 125 | return new Hufnagel(); 126 | case "Kamenetskiy1": 127 | proj = new CylindricalStereographic(); 128 | proj.setStandardParallel(55 / 180 * Math.PI); 129 | return proj; 130 | case "Kavrayskiy1": 131 | return new Kavrayskiy1(); 132 | case "KharchenkoShabanova": 133 | return new KharchenkoShabanova(); 134 | case "LambertEqualAreaCylindrical": 135 | return new LambertEqualAreaCylindrical(); 136 | case "Mercator": 137 | return new Mercator(); 138 | case "Miller": 139 | return new Miller(); 140 | case "Miller2": 141 | return new Miller2(); 142 | case "MillerGall": 143 | proj = new CylindricalStereographic(); 144 | // standard parallel at 66.16 degrees 145 | proj.setStandardParallel(2 / Math.sqrt(3)); 146 | return proj; 147 | case "MillerPerspective": 148 | return new MillerPerspective(); 149 | case "Mollweide": 150 | return new Mollweide(); 151 | case "NaturalEarth": 152 | return new NaturalEarth(); 153 | case "Pavlov": 154 | return new Pavlov(); 155 | case "PlateCarree": 156 | proj = new Equirectangular(); 157 | proj.setStandardParallel(0); 158 | return proj; 159 | case "RMillerMinOverallScaleDistortion": 160 | proj = new Equirectangular(); 161 | proj.setStandardParallel(37.5 / 180 * Math.PI); 162 | return proj; 163 | case "RMillerMinContinentalScaleDistortion": 164 | proj = new Equirectangular(); 165 | proj.setStandardParallel(43.5 / 180 * Math.PI); 166 | return proj; 167 | case "Robinson": 168 | return new Robinson(); 169 | case "Sinusoidal": 170 | return new Sinusoidal(); 171 | case "Strebe1995": 172 | return new Strebe1995(); 173 | case "Tobler1": 174 | return new Tobler1(); 175 | case "Tobler2": 176 | return new Tobler2(); 177 | case "Urmayev2": 178 | return new Urmayev2(); 179 | case "Urmayev3": 180 | return new Urmayev3(); 181 | case "Wagner7": 182 | return new Wagner7(); 183 | case "LambertAzimuthalEqualAreaPolar": 184 | return new LambertAzimuthalEqualAreaPolar(); 185 | default: 186 | return null; 187 | } 188 | } 189 | 190 | function setForward1() {"use strict"; 191 | map.getProjection().setForward1(getProjection($("#forward1-menu").val())); 192 | map.render(); 193 | } 194 | 195 | function setInverse() {"use strict"; 196 | map.getProjection().setInverse(getProjection($("#inverse-menu").val())); 197 | map.render(); 198 | } 199 | 200 | function setForward2() {"use strict"; 201 | map.getProjection().setForward2(getProjection($("#forward2-menu").val())); 202 | map.render(); 203 | } 204 | 205 | $(window).load(function() {"use strict"; 206 | 207 | // currently styling works like this: 208 | // - if there's a fillStyle then it will be filled 209 | // - if there's a strokeStyle then it will be stroked 210 | // - points are always 3px rectangles 211 | // - polylines can't be filled 212 | 213 | var layers = []; 214 | 215 | function Style(strokeStyle, lineWidth, fillStyle) { 216 | if (strokeStyle !== undefined) { 217 | this.strokeStyle = strokeStyle; 218 | this.lineWidth = lineWidth; 219 | } 220 | if (fillStyle !== undefined) { 221 | this.fillStyle = fillStyle; 222 | } 223 | } 224 | 225 | //create the map 226 | layers.push(new Layer("../data/ne_110m_coastline", new Style("#888", "1"))); 227 | layers.push(new Graticule(new Style("#77b", "1"), 15)); 228 | 229 | map = createMap(layers, new Strebe1995(), $("#mapCanvas")[0], $("#mapCanvas").width(), $("#mapCanvas").height()); 230 | $(window).resize(function() { 231 | map.resize($("#mapCanvas").width(), $("#mapCanvas").height()); 232 | map.setScale(getMapScale(map)); 233 | }); 234 | 235 | map.setScale(getMapScale(map)); 236 | 237 | // init UI controls 238 | $("#map").bind("mousedown", mouseEventHandler); 239 | 240 | initSlider(); 241 | 242 | // ie 243 | $("#mapCanvas")[0].onselectstart = function() { 244 | return false; 245 | }; 246 | // mozilla 247 | $("#mapCanvas")[0].onmousedown = function() { 248 | return false; 249 | }; 250 | 251 | $("#forward1-menu").bind("change", setForward1); 252 | $("#inverse-menu").bind("change", setInverse); 253 | $("#forward2-menu").bind("change", setForward2); 254 | 255 | }); 256 | 257 | -------------------------------------------------------------------------------- /StrebeTransformation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Strebe Transformation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 | First Forward Projection 22 | 65 | 66 |
Inverse Projection
67 | 75 | 76 |
Second Forward Projection
77 | 120 | 121 |
122 |
123 |
124 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /StrebeTransformation/static.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eee; 3 | font: 12px sans-serif; 4 | } 5 | 6 | .map { 7 | height:500px; 8 | width:100%; 9 | padding:0px; 10 | border:0px; 11 | background-color: #fff; 12 | position:relative; 13 | } 14 | 15 | .mapCanvas { 16 | width: 100%; 17 | height: 100%; 18 | position:absolute; 19 | } 20 | 21 | #meridianContainer { 22 | position: relative; 23 | margin-left:auto; 24 | margin-right:auto; 25 | width:380px; 26 | } 27 | 28 | #meridianSlider{ 29 | position: relative; 30 | width:300px; 31 | float: left; 32 | } 33 | 34 | #meridianText{ 35 | position: relative; 36 | width: 70px; 37 | margin-left: 5px; 38 | float: left; 39 | } -------------------------------------------------------------------------------- /data/ne_110m_coastline.VERSION.txt: -------------------------------------------------------------------------------- 1 | 2.0.0 -------------------------------------------------------------------------------- /data/ne_110m_coastline.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/data/ne_110m_coastline.dbf -------------------------------------------------------------------------------- /data/ne_110m_coastline.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /data/ne_110m_coastline.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/data/ne_110m_coastline.shp -------------------------------------------------------------------------------- /data/ne_110m_coastline.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OSUCartography/CanvasMap/66ee766eae2184f309703902dfe576c1fe9a601c/data/ne_110m_coastline.shx --------------------------------------------------------------------------------