├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── findbugs-idea.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── jenkinsSettings.xml ├── jsLibraryMappings.xml ├── jsLinters │ ├── jshint.xml │ └── jslint.xml ├── libraries │ └── ofp_viewer_node_modules.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml ├── uiDesigner.xml └── vcs.xml ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── app ├── .buildignore ├── .htaccess ├── 404.html ├── favicon.ico ├── index.html ├── media │ └── public │ │ └── svg │ │ └── 065-01.svg ├── robots.txt ├── scripts │ ├── app.js │ └── controllers │ │ └── floorplan.js ├── styles │ └── main.css └── views │ └── floorplan.html ├── bower.json ├── convertSVG.js ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test ├── runner.html └── spec └── controllers ├── floorplan.js └── main.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/components 6 | app/media/internal 7 | workspace.xml 8 | atlassian-ide-plugin.xml 9 | ofp-viewer.iml 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfloorplan/ofp-viewer/d36fd5da52f0b304cfb15d951046aa9e3b94642e/.gitmodules -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ofp-viewer -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/findbugs-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 31 | 190 | 203 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/jenkinsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/jshint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 62 | -------------------------------------------------------------------------------- /.idea/jsLinters/jslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/ofp_viewer_node_modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "es5": true, 5 | "esnext": true, 6 | "bitwise": true, 7 | "camelcase": true, 8 | "curly": true, 9 | "eqeqeq": true, 10 | "immed": true, 11 | "indent": 2, 12 | "latedef": true, 13 | "newcap": true, 14 | "noarg": true, 15 | "quotmark": "single", 16 | "regexp": true, 17 | "undef": true, 18 | "unused": true, 19 | "strict": true, 20 | "trailing": true, 21 | "smarttabs": true, 22 | "globals": { 23 | "angular": false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet; 3 | var mountFolder = function (connect, dir) { 4 | return connect.static(require('path').resolve(dir)); 5 | }; 6 | 7 | module.exports = function (grunt) { 8 | // load all grunt tasks 9 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 10 | 11 | // configurable paths 12 | var yeomanConfig = { 13 | app: 'app', 14 | dist: 'dist' 15 | }; 16 | 17 | try { 18 | yeomanConfig.app = require('./component.json').appPath || yeomanConfig.app; 19 | } catch (e) {} 20 | 21 | grunt.initConfig({ 22 | yeoman: yeomanConfig, 23 | watch: { 24 | coffee: { 25 | files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], 26 | tasks: ['coffee:dist'] 27 | }, 28 | coffeeTest: { 29 | files: ['test/spec/{,*/}*.coffee'], 30 | tasks: ['coffee:test'] 31 | }, 32 | compass: { 33 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 34 | tasks: ['compass'] 35 | }, 36 | livereload: { 37 | files: [ 38 | '<%= yeoman.app %>/{,*/}*.html', 39 | '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', 40 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', 41 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 42 | ], 43 | tasks: ['livereload'] 44 | } 45 | }, 46 | connect: { 47 | options: { 48 | port: 9000, 49 | // Change this to '0.0.0.0' to access the server from outside. 50 | hostname: 'localhost' 51 | }, 52 | livereload: { 53 | options: { 54 | middleware: function (connect) { 55 | return [ 56 | lrSnippet, 57 | mountFolder(connect, '.tmp'), 58 | mountFolder(connect, yeomanConfig.app) 59 | ]; 60 | } 61 | } 62 | }, 63 | test: { 64 | options: { 65 | middleware: function (connect) { 66 | return [ 67 | mountFolder(connect, '.tmp'), 68 | mountFolder(connect, 'test') 69 | ]; 70 | } 71 | } 72 | } 73 | }, 74 | open: { 75 | server: { 76 | url: 'http://localhost:<%= connect.options.port %>' 77 | } 78 | }, 79 | clean: { 80 | dist: { 81 | files: [{ 82 | dot: true, 83 | src: [ 84 | '.tmp', 85 | '<%= yeoman.dist %>/*', 86 | '!<%= yeoman.dist %>/.git*' 87 | ] 88 | }] 89 | }, 90 | server: '.tmp' 91 | }, 92 | jshint: { 93 | options: { 94 | jshintrc: '.jshintrc' 95 | }, 96 | all: [ 97 | 'Gruntfile.js', 98 | '<%= yeoman.app %>/scripts/{,*/}*.js' 99 | ] 100 | }, 101 | karma: { 102 | unit: { 103 | configFile: 'karma.conf.js', 104 | singleRun: true 105 | } 106 | }, 107 | coffee: { 108 | dist: { 109 | files: [{ 110 | expand: true, 111 | cwd: '<%= yeoman.app %>/scripts', 112 | src: '{,*/}*.coffee', 113 | dest: '.tmp/scripts', 114 | ext: '.js' 115 | }] 116 | }, 117 | test: { 118 | files: [{ 119 | expand: true, 120 | cwd: 'test/spec', 121 | src: '{,*/}*.coffee', 122 | dest: '.tmp/spec', 123 | ext: '.js' 124 | }] 125 | } 126 | }, 127 | compass: { 128 | options: { 129 | sassDir: '<%= yeoman.app %>/styles', 130 | cssDir: '.tmp/styles', 131 | imagesDir: '<%= yeoman.app %>/images', 132 | javascriptsDir: '<%= yeoman.app %>/scripts', 133 | fontsDir: '<%= yeoman.app %>/styles/fonts', 134 | importPath: '<%= yeoman.app %>/components', 135 | relativeAssets: true 136 | }, 137 | dist: {}, 138 | server: { 139 | options: { 140 | debugInfo: true 141 | } 142 | } 143 | }, 144 | concat: { 145 | dist: { 146 | files: { 147 | '<%= yeoman.dist %>/scripts/scripts.js': [ 148 | '.tmp/scripts/{,*/}*.js', 149 | '<%= yeoman.app %>/scripts/{,*/}*.js' 150 | ] 151 | } 152 | } 153 | }, 154 | useminPrepare: { 155 | html: '<%= yeoman.app %>/index.html', 156 | options: { 157 | dest: '<%= yeoman.dist %>' 158 | } 159 | }, 160 | usemin: { 161 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 162 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 163 | options: { 164 | dirs: ['<%= yeoman.dist %>'] 165 | } 166 | }, 167 | imagemin: { 168 | dist: { 169 | files: [{ 170 | expand: true, 171 | cwd: '<%= yeoman.app %>/images', 172 | src: '{,*/}*.{png,jpg,jpeg}', 173 | dest: '<%= yeoman.dist %>/images' 174 | }] 175 | } 176 | }, 177 | cssmin: { 178 | dist: { 179 | files: { 180 | '<%= yeoman.dist %>/styles/main.css': [ 181 | '.tmp/styles/{,*/}*.css', 182 | '<%= yeoman.app %>/styles/{,*/}*.css' 183 | ] 184 | } 185 | } 186 | }, 187 | htmlmin: { 188 | dist: { 189 | options: { 190 | /*removeCommentsFromCDATA: true, 191 | // https://github.com/yeoman/grunt-usemin/issues/44 192 | //collapseWhitespace: true, 193 | collapseBooleanAttributes: true, 194 | removeAttributeQuotes: true, 195 | removeRedundantAttributes: true, 196 | useShortDoctype: true, 197 | removeEmptyAttributes: true, 198 | removeOptionalTags: true*/ 199 | }, 200 | files: [{ 201 | expand: true, 202 | cwd: '<%= yeoman.app %>', 203 | src: ['*.html', 'views/*.html'], 204 | dest: '<%= yeoman.dist %>' 205 | }] 206 | } 207 | }, 208 | cdnify: { 209 | dist: { 210 | html: ['<%= yeoman.dist %>/*.html'] 211 | } 212 | }, 213 | ngmin: { 214 | dist: { 215 | files: [{ 216 | expand: true, 217 | cwd: '<%= yeoman.dist %>/scripts', 218 | src: '*.js', 219 | dest: '<%= yeoman.dist %>/scripts' 220 | }] 221 | } 222 | }, 223 | uglify: { 224 | dist: { 225 | files: { 226 | '<%= yeoman.dist %>/scripts/scripts.js': [ 227 | '<%= yeoman.dist %>/scripts/scripts.js' 228 | ] 229 | } 230 | } 231 | }, 232 | rev: { 233 | dist: { 234 | files: { 235 | src: [ 236 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 237 | '<%= yeoman.dist %>/styles/{,*/}*.css', 238 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 239 | '<%= yeoman.dist %>/styles/fonts/*' 240 | ] 241 | } 242 | } 243 | }, 244 | copy: { 245 | dist: { 246 | files: [{ 247 | expand: true, 248 | dot: true, 249 | cwd: '<%= yeoman.app %>', 250 | dest: '<%= yeoman.dist %>', 251 | src: [ 252 | '*.{ico,txt}', 253 | '.htaccess', 254 | 'components/**/*', 255 | 'images/{,*/}*.{gif,webp}', 256 | 'styles/fonts/*' 257 | ] 258 | }] 259 | } 260 | } 261 | }); 262 | 263 | grunt.renameTask('regarde', 'watch'); 264 | 265 | grunt.registerTask('server', [ 266 | 'clean:server', 267 | //'coffee:dist', 268 | //'compass:server', 269 | 'livereload-start', 270 | 'connect:livereload', 271 | 'open', 272 | 'watch' 273 | ]); 274 | 275 | grunt.registerTask('test', [ 276 | 'clean:server', 277 | 'coffee', 278 | 'compass', 279 | 'connect:test', 280 | 'karma' 281 | ]); 282 | 283 | grunt.registerTask('build', [ 284 | 'clean:dist', 285 | 'jshint', 286 | 'test', 287 | 'coffee', 288 | 'compass:dist', 289 | 'useminPrepare', 290 | 'imagemin', 291 | 'cssmin', 292 | 'htmlmin', 293 | 'concat', 294 | 'copy', 295 | 'cdnify', 296 | 'ngmin', 297 | 'uglify', 298 | 'rev', 299 | 'usemin' 300 | ]); 301 | 302 | grunt.registerTask('default', ['build']); 303 | }; 304 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 SYNCADD Systems, Inc. 2 | 3 | This work is free. You can redistribute it and/or modify it under the 4 | terms of the MIT License. 5 | 6 | The MIT License (MIT) 7 | ---------------- 8 | 9 | Copyright (c) 2013 SYNCADD Systems, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenFloorPlan Viewer 2 | 3 | View and Interact with SVG Floorplans using HTML5 4 | 5 | 6 | ## Development 7 | 8 | Requirements: 9 | 10 | - NodeJS 11 | - Yeoman (including Bower and Grunt) https://github.com/yeoman/yeoman/wiki/Getting-Started 12 | 13 | 14 | ``` 15 | npm install -g yo 16 | npm install -g grunt-cli 17 | npm install -g bower 18 | ``` 19 | 20 | 21 | Install using: 22 | 23 | ``` 24 | git clone https://github.com/openfloorplan/ofp-viewer.git 25 | cd ofp-viewer 26 | npm install 27 | bower install 28 | ``` 29 | 30 | Angular Bootstrap needs to be built seperately 31 | 32 | ``` 33 | cd app/components/angular-ui-bootstrap 34 | npm install 35 | grunt 36 | grunt build 37 | ``` 38 | 39 | 40 | Return to root directory and run server with: 41 | 42 | cd ../../.. 43 | grunt server 44 | 45 | This code currently referencing a missing app/media folder that contains floorplans we use for demos. 46 | We are working on replacing the demos with data that we can share as part of the project. 47 | In the mean time, you will need to reconfigure it to use your own floorplans. 48 | 49 | ## Demo Data 50 | 51 | This project uses SVG data converted from CAD floorplans such as this example http://www.physics.ohio-state.edu/fac_engr/flr_plans.html 52 | 53 | (It isn't easy to find good public CAD data, they deserve credit for publishing their CAD files online.) 54 | 55 | You will need to convert the DWG files into SVG using something like GDAL ogr2ogr http://www.gdal.org/drv_cad.html 56 | 57 | 58 | ## Setting up Your Own Floor Plans 59 | 60 | ###Requirements: 61 | 62 | * The spaces in your floorplan need to be drawn as polygons. 63 | * Polygons need to grouped using the SVG tag and each group should have a unique ID 64 | 65 | ###Here is an example: 66 | 67 | ``` 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | Update floorplan.js to point to the layer in your floorplan that contains spaces. 75 | 76 | https://github.com/openfloorplan/ofp-viewer/blob/master/app/scripts/controllers/floorplan.js 77 | 78 | Specifically you will need to configure a layer type for each group of polygons you want to be interactive. You can see the code for LayerType here: https://github.com/openfloorplan/ofp.js/blob/master/src/core/layerType.js but the inputs are: 79 | 80 | ``` 81 | ofp.LayerType('MyName', ‘my-class-name', [‘d3 selector string']); 82 | ``` 83 | 84 | The first argument is just a name you pick, the second is a class name you also pick that is injected into the SVG to simplify future interactions (e.g. applying style changes with a class), then last one is the important one. That is a D3.js selector (similar to JQuery selectors) that selects the the SVG group tag () that has that id. 85 | 86 | ---------------- 87 | 88 | 89 | ## License 90 | 91 | OpenFloorPlan Viewer (ofp-viewer) is released under the MIT license, see LICENSE file. 92 | 93 | -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache configuration file 2 | # httpd.apache.org/docs/2.2/mod/quickreference.html 3 | 4 | # Note .htaccess files are an overhead, this logic should be in your Apache 5 | # config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html 6 | 7 | # Techniques in here adapted from all over, including: 8 | # Kroc Camen: camendesign.com/.htaccess 9 | # perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/ 10 | # Sample .htaccess file of CMS MODx: modxcms.com 11 | 12 | 13 | # ---------------------------------------------------------------------- 14 | # Better website experience for IE users 15 | # ---------------------------------------------------------------------- 16 | 17 | # Force the latest IE version, in various cases when it may fall back to IE7 mode 18 | # github.com/rails/rails/commit/123eb25#commitcomment-118920 19 | # Use ChromeFrame if it's installed for a better experience for the poor IE folk 20 | 21 | 22 | Header set X-UA-Compatible "IE=Edge,chrome=1" 23 | # mod_headers can't match by content-type, but we don't want to send this header on *everything*... 24 | 25 | Header unset X-UA-Compatible 26 | 27 | 28 | 29 | 30 | # ---------------------------------------------------------------------- 31 | # Cross-domain AJAX requests 32 | # ---------------------------------------------------------------------- 33 | 34 | # Serve cross-domain Ajax requests, disabled by default. 35 | # enable-cors.org 36 | # code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 37 | 38 | # 39 | # Header set Access-Control-Allow-Origin "*" 40 | # 41 | 42 | 43 | # ---------------------------------------------------------------------- 44 | # CORS-enabled images (@crossorigin) 45 | # ---------------------------------------------------------------------- 46 | 47 | # Send CORS headers if browsers request them; enabled by default for images. 48 | # developer.mozilla.org/en/CORS_Enabled_Image 49 | # blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 50 | # hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 51 | # wiki.mozilla.org/Security/Reviews/crossoriginAttribute 52 | 53 | 54 | 55 | # mod_headers, y u no match by Content-Type?! 56 | 57 | SetEnvIf Origin ":" IS_CORS 58 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 59 | 60 | 61 | 62 | 63 | 64 | # ---------------------------------------------------------------------- 65 | # Webfont access 66 | # ---------------------------------------------------------------------- 67 | 68 | # Allow access from all domains for webfonts. 69 | # Alternatively you could only whitelist your 70 | # subdomains like "subdomain.example.com". 71 | 72 | 73 | 74 | Header set Access-Control-Allow-Origin "*" 75 | 76 | 77 | 78 | 79 | # ---------------------------------------------------------------------- 80 | # Proper MIME type for all files 81 | # ---------------------------------------------------------------------- 82 | 83 | # JavaScript 84 | # Normalize to standard type (it's sniffed in IE anyways) 85 | # tools.ietf.org/html/rfc4329#section-7.2 86 | AddType application/javascript js jsonp 87 | AddType application/json json 88 | 89 | # Audio 90 | AddType audio/ogg oga ogg 91 | AddType audio/mp4 m4a f4a f4b 92 | 93 | # Video 94 | AddType video/ogg ogv 95 | AddType video/mp4 mp4 m4v f4v f4p 96 | AddType video/webm webm 97 | AddType video/x-flv flv 98 | 99 | # SVG 100 | # Required for svg webfonts on iPad 101 | # twitter.com/FontSquirrel/status/14855840545 102 | AddType image/svg+xml svg svgz 103 | AddEncoding gzip svgz 104 | 105 | # Webfonts 106 | AddType application/vnd.ms-fontobject eot 107 | AddType application/x-font-ttf ttf ttc 108 | AddType font/opentype otf 109 | AddType application/x-font-woff woff 110 | 111 | # Assorted types 112 | AddType image/x-icon ico 113 | AddType image/webp webp 114 | AddType text/cache-manifest appcache manifest 115 | AddType text/x-component htc 116 | AddType application/xml rss atom xml rdf 117 | AddType application/x-chrome-extension crx 118 | AddType application/x-opera-extension oex 119 | AddType application/x-xpinstall xpi 120 | AddType application/octet-stream safariextz 121 | AddType application/x-web-app-manifest+json webapp 122 | AddType text/x-vcard vcf 123 | AddType application/x-shockwave-flash swf 124 | AddType text/vtt vtt 125 | 126 | 127 | # ---------------------------------------------------------------------- 128 | # Allow concatenation from within specific js and css files 129 | # ---------------------------------------------------------------------- 130 | 131 | # e.g. Inside of script.combined.js you could have 132 | # 133 | # 134 | # and they would be included into this single file. 135 | 136 | # This is not in use in the boilerplate as it stands. You may 137 | # choose to use this technique if you do not have a build process. 138 | 139 | # 140 | # Options +Includes 141 | # AddOutputFilterByType INCLUDES application/javascript application/json 142 | # SetOutputFilter INCLUDES 143 | # 144 | 145 | # 146 | # Options +Includes 147 | # AddOutputFilterByType INCLUDES text/css 148 | # SetOutputFilter INCLUDES 149 | # 150 | 151 | 152 | # ---------------------------------------------------------------------- 153 | # Gzip compression 154 | # ---------------------------------------------------------------------- 155 | 156 | 157 | 158 | # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ 159 | 160 | 161 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 162 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 163 | 164 | 165 | 166 | # HTML, TXT, CSS, JavaScript, JSON, XML, HTC: 167 | 168 | FilterDeclare COMPRESS 169 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html 170 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css 171 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain 172 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml 173 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component 174 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript 175 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json 176 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml 177 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml 178 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml 179 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml 180 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject 181 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml 182 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon 183 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf 184 | FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype 185 | FilterChain COMPRESS 186 | FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no 187 | 188 | 189 | 190 | # Legacy versions of Apache 191 | AddOutputFilterByType DEFLATE text/html text/plain text/css application/json 192 | AddOutputFilterByType DEFLATE application/javascript 193 | AddOutputFilterByType DEFLATE text/xml application/xml text/x-component 194 | AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml 195 | AddOutputFilterByType DEFLATE image/x-icon image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype 196 | 197 | 198 | 199 | 200 | 201 | # ---------------------------------------------------------------------- 202 | # Expires headers (for better cache control) 203 | # ---------------------------------------------------------------------- 204 | 205 | # These are pretty far-future expires headers. 206 | # They assume you control versioning with filename-based cache busting 207 | # Additionally, consider that outdated proxies may miscache 208 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ 209 | 210 | # If you don't use filenames to version, lower the CSS and JS to something like 211 | # "access plus 1 week". 212 | 213 | 214 | ExpiresActive on 215 | 216 | # Perhaps better to whitelist expires rules? Perhaps. 217 | ExpiresDefault "access plus 1 month" 218 | 219 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) 220 | ExpiresByType text/cache-manifest "access plus 0 seconds" 221 | 222 | # Your document html 223 | ExpiresByType text/html "access plus 0 seconds" 224 | 225 | # Data 226 | ExpiresByType text/xml "access plus 0 seconds" 227 | ExpiresByType application/xml "access plus 0 seconds" 228 | ExpiresByType application/json "access plus 0 seconds" 229 | 230 | # Feed 231 | ExpiresByType application/rss+xml "access plus 1 hour" 232 | ExpiresByType application/atom+xml "access plus 1 hour" 233 | 234 | # Favicon (cannot be renamed) 235 | ExpiresByType image/x-icon "access plus 1 week" 236 | 237 | # Media: images, video, audio 238 | ExpiresByType image/gif "access plus 1 month" 239 | ExpiresByType image/png "access plus 1 month" 240 | ExpiresByType image/jpeg "access plus 1 month" 241 | ExpiresByType video/ogg "access plus 1 month" 242 | ExpiresByType audio/ogg "access plus 1 month" 243 | ExpiresByType video/mp4 "access plus 1 month" 244 | ExpiresByType video/webm "access plus 1 month" 245 | 246 | # HTC files (css3pie) 247 | ExpiresByType text/x-component "access plus 1 month" 248 | 249 | # Webfonts 250 | ExpiresByType application/x-font-ttf "access plus 1 month" 251 | ExpiresByType font/opentype "access plus 1 month" 252 | ExpiresByType application/x-font-woff "access plus 1 month" 253 | ExpiresByType image/svg+xml "access plus 1 month" 254 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 255 | 256 | # CSS and JavaScript 257 | ExpiresByType text/css "access plus 1 year" 258 | ExpiresByType application/javascript "access plus 1 year" 259 | 260 | 261 | 262 | 263 | # ---------------------------------------------------------------------- 264 | # Prevent mobile network providers from modifying your site 265 | # ---------------------------------------------------------------------- 266 | 267 | # The following header prevents modification of your code over 3G on some 268 | # European providers. 269 | # This is the official 'bypass' suggested by O2 in the UK. 270 | 271 | # 272 | # Header set Cache-Control "no-transform" 273 | # 274 | 275 | 276 | # ---------------------------------------------------------------------- 277 | # ETag removal 278 | # ---------------------------------------------------------------------- 279 | 280 | # FileETag None is not enough for every server. 281 | 282 | Header unset ETag 283 | 284 | 285 | # Since we're sending far-future expires, we don't need ETags for 286 | # static content. 287 | # developer.yahoo.com/performance/rules.html#etags 288 | FileETag None 289 | 290 | 291 | # ---------------------------------------------------------------------- 292 | # Stop screen flicker in IE on CSS rollovers 293 | # ---------------------------------------------------------------------- 294 | 295 | # The following directives stop screen flicker in IE on CSS rollovers - in 296 | # combination with the "ExpiresByType" rules for images (see above). 297 | 298 | # BrowserMatch "MSIE" brokenvary=1 299 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 300 | # BrowserMatch "Opera" !brokenvary 301 | # SetEnvIf brokenvary 1 force-no-vary 302 | 303 | 304 | # ---------------------------------------------------------------------- 305 | # Set Keep-Alive Header 306 | # ---------------------------------------------------------------------- 307 | 308 | # Keep-Alive allows the server to send multiple requests through one 309 | # TCP-connection. Be aware of possible disadvantages of this setting. Turn on 310 | # if you serve a lot of static content. 311 | 312 | # 313 | # Header set Connection Keep-Alive 314 | # 315 | 316 | 317 | # ---------------------------------------------------------------------- 318 | # Cookie setting from iframes 319 | # ---------------------------------------------------------------------- 320 | 321 | # Allow cookies to be set from iframes (for IE only) 322 | # If needed, specify a path or regex in the Location directive. 323 | 324 | # 325 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 326 | # 327 | 328 | 329 | # ---------------------------------------------------------------------- 330 | # Start rewrite engine 331 | # ---------------------------------------------------------------------- 332 | 333 | # Turning on the rewrite engine is necessary for the following rules and 334 | # features. FollowSymLinks must be enabled for this to work. 335 | 336 | # Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN 337 | # If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where 338 | # 'foo' is your directory. 339 | 340 | # If your web host doesn't allow the FollowSymlinks option, you may need to 341 | # comment it out and use `Options +SymLinksOfOwnerMatch`, but be aware of the 342 | # performance impact: http://goo.gl/Mluzd 343 | 344 | 345 | Options +FollowSymlinks 346 | # Options +SymLinksIfOwnerMatch 347 | Options +FollowSymlinks 348 | RewriteEngine On 349 | # RewriteBase / 350 | 351 | 352 | 353 | # ---------------------------------------------------------------------- 354 | # Suppress or force the "www." at the beginning of URLs 355 | # ---------------------------------------------------------------------- 356 | 357 | # The same content should never be available under two different URLs - 358 | # especially not with and without "www." at the beginning, since this can cause 359 | # SEO problems (duplicate content). That's why you should choose one of the 360 | # alternatives and redirect the other one. 361 | 362 | # By default option 1 (no "www.") is activated. 363 | # no-www.org/faq.php?q=class_b 364 | 365 | # If you'd prefer to use option 2, just comment out all option 1 lines 366 | # and uncomment option 2. 367 | 368 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 369 | 370 | # ---------------------------------------------------------------------- 371 | 372 | # Option 1: 373 | # Rewrite "www.example.com -> example.com". 374 | 375 | 376 | RewriteCond %{HTTPS} !=on 377 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 378 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 379 | 380 | 381 | # ---------------------------------------------------------------------- 382 | 383 | # Option 2: 384 | # Rewrite "example.com -> www.example.com". 385 | # Be aware that the following rule might not be a good idea if you use "real" 386 | # subdomains for certain parts of your website. 387 | 388 | # 389 | # RewriteCond %{HTTPS} !=on 390 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 391 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 392 | # 393 | 394 | 395 | # ---------------------------------------------------------------------- 396 | # Built-in filename-based cache busting 397 | # ---------------------------------------------------------------------- 398 | 399 | # If you're not using the build script to manage your filename version revving, 400 | # you might want to consider enabling this, which will route requests for 401 | # /css/style.20110203.css to /css/style.css 402 | 403 | # To understand why this is important and a better idea than all.css?v1231, 404 | # read: github.com/h5bp/html5-boilerplate/wiki/cachebusting 405 | 406 | # 407 | # RewriteCond %{REQUEST_FILENAME} !-f 408 | # RewriteCond %{REQUEST_FILENAME} !-d 409 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 410 | # 411 | 412 | 413 | # ---------------------------------------------------------------------- 414 | # Prevent SSL cert warnings 415 | # ---------------------------------------------------------------------- 416 | 417 | # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent 418 | # https://www.example.com when your cert only allows https://secure.example.com 419 | 420 | # 421 | # RewriteCond %{SERVER_PORT} !^443 422 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 423 | # 424 | 425 | 426 | # ---------------------------------------------------------------------- 427 | # Prevent 404 errors for non-existing redirected folders 428 | # ---------------------------------------------------------------------- 429 | 430 | # without -MultiViews, Apache will give a 404 for a rewrite if a folder of the 431 | # same name does not exist. 432 | # webmasterworld.com/apache/3808792.htm 433 | 434 | Options -MultiViews 435 | 436 | 437 | # ---------------------------------------------------------------------- 438 | # Custom 404 page 439 | # ---------------------------------------------------------------------- 440 | 441 | # You can add custom pages to handle 500 or 403 pretty easily, if you like. 442 | # If you are hosting your site in subdirectory, adjust this accordingly 443 | # e.g. ErrorDocument 404 /subdir/404.html 444 | ErrorDocument 404 /404.html 445 | 446 | 447 | # ---------------------------------------------------------------------- 448 | # UTF-8 encoding 449 | # ---------------------------------------------------------------------- 450 | 451 | # Use UTF-8 encoding for anything served text/plain or text/html 452 | AddDefaultCharset utf-8 453 | 454 | # Force UTF-8 for a number of file formats 455 | AddCharset utf-8 .atom .css .js .json .rss .vtt .xml 456 | 457 | 458 | # ---------------------------------------------------------------------- 459 | # A little more security 460 | # ---------------------------------------------------------------------- 461 | 462 | # To avoid displaying the exact version number of Apache being used, add the 463 | # following to httpd.conf (it will not work in .htaccess): 464 | # ServerTokens Prod 465 | 466 | # "-Indexes" will have Apache block users from browsing folders without a 467 | # default document Usually you should leave this activated, because you 468 | # shouldn't allow everybody to surf through every folder on your server (which 469 | # includes rather private places like CMS system folders). 470 | 471 | Options -Indexes 472 | 473 | 474 | # Block access to "hidden" directories or files whose names begin with a 475 | # period. This includes directories used by version control systems such as 476 | # Subversion or Git. 477 | 478 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 479 | RewriteCond %{SCRIPT_FILENAME} -f 480 | RewriteRule "(^|/)\." - [F] 481 | 482 | 483 | # Block access to backup and source files. These files may be left by some 484 | # text/html editors and pose a great security danger, when anyone can access 485 | # them. 486 | 487 | Order allow,deny 488 | Deny from all 489 | Satisfy All 490 | 491 | 492 | # If your server is not already configured as such, the following directive 493 | # should be uncommented in order to set PHP's register_globals option to OFF. 494 | # This closes a major security hole that is abused by most XSS (cross-site 495 | # scripting) attacks. For more information: http://php.net/register_globals 496 | # 497 | # IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS: 498 | # 499 | # Your server does not allow PHP directives to be set via .htaccess. In that 500 | # case you must make this change in your php.ini file instead. If you are 501 | # using a commercial web host, contact the administrators for assistance in 502 | # doing this. Not all servers allow local php.ini files, and they should 503 | # include all PHP configurations (not just this one), or you will effectively 504 | # reset everything to PHP defaults. Consult www.php.net for more detailed 505 | # information about setting PHP directives. 506 | 507 | # php_flag register_globals Off 508 | 509 | # Rename session cookie to something else, than PHPSESSID 510 | # php_value session.name sid 511 | 512 | # Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.) 513 | # php_flag magic_quotes_gpc Off 514 | 515 | # Do not show you are using PHP 516 | # Note: Move this line to php.ini since it won't work in .htaccess 517 | # php_flag expose_php Off 518 | 519 | # Level of log detail - log all errors 520 | # php_value error_reporting -1 521 | 522 | # Write errors to log file 523 | # php_flag log_errors On 524 | 525 | # Do not display errors in browser (production - Off, development - On) 526 | # php_flag display_errors Off 527 | 528 | # Do not display startup errors (production - Off, development - On) 529 | # php_flag display_startup_errors Off 530 | 531 | # Format errors in plain text 532 | # Note: Leave this setting 'On' for xdebug's var_dump() output 533 | # php_flag html_errors Off 534 | 535 | # Show multiple occurrence of error 536 | # php_flag ignore_repeated_errors Off 537 | 538 | # Show same errors from different sources 539 | # php_flag ignore_repeated_source Off 540 | 541 | # Size limit for error messages 542 | # php_value log_errors_max_len 1024 543 | 544 | # Don't precede error with string (doesn't accept empty string, use whitespace if you need) 545 | # php_value error_prepend_string " " 546 | 547 | # Don't prepend to error (doesn't accept empty string, use whitespace if you need) 548 | # php_value error_append_string " " 549 | 550 | # Increase cookie security 551 | 552 | php_value session.cookie_httponly true 553 | 554 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 |
    148 |
  • a mistyped address
  • 149 |
  • an out-of-date link
  • 150 |
151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfloorplan/ofp-viewer/d36fd5da52f0b304cfb15d951046aa9e3b94642e/app/favicon.ico -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | OpenFloorPlan Viewer 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 31 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 55 | 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | var floorplanApp = angular.module('floorplanApp', ['ui', 'ui.bootstrap', 'ngRoute']) 2 | .config(function ($routeProvider, $locationProvider) { 3 | 'use strict'; 4 | $locationProvider.html5Mode(true); 5 | $routeProvider 6 | .when('/', { 7 | templateUrl: 'views/floorplan.html', 8 | controller: 'FloorplanCtrl' 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /app/scripts/controllers/floorplan.js: -------------------------------------------------------------------------------- 1 | L.CRS.Simple2 = L.extend({}, L.CRS, { 2 | projection: L.Projection.LonLat, 3 | transformation: new L.Transformation(1, 0, -1, 0), 4 | 5 | scale: function (zoom) { 6 | return Math.pow(2, zoom); 7 | } 8 | }); 9 | 10 | floorplanApp.controller('FloorplanCtrl', function ($scope) { 11 | 'use strict'; 12 | 13 | 14 | $scope.fileSVGPath = "/svg/"; 15 | $scope.filePNGPath = "/png/"; 16 | 17 | $scope.fileExt = ".svg"; 18 | $scope.fileOL = "-ol.svg"; 19 | $scope.fileBGUL = "-bg-ul.jpg"; 20 | $scope.fileBGLL = "-bg-ll.jpg"; 21 | $scope.fileBGUR = "-bg-ur.jpg"; 22 | $scope.fileBGLR = "-bg-lr.jpg"; 23 | 24 | //Create layer types for SYNCADD floorplans 25 | $scope.layerTypes = {}; 26 | $scope.layerTypes.spaces = new ofp.LayerType('Space', 'ofp-space', ['#bgspa_space_area_b']); 27 | $scope.layerTypes.columns = new ofp.LayerType('Column', 'ofp-column', ['#Column', '#bgspa_column_area_b']); 28 | $scope.layerTypes.constructions = new ofp.LayerType('Construction', 'ofp-construction', ['#Constructions', '#Frames']); 29 | $scope.layerTypes.dimensionAnnotations = new ofp.LayerType('Dimension Annotations', 'ofp-annotations-dimensions', ['#Dimension', '#A-ANNO-DIMS']); 30 | 31 | $scope.floorplanList = [ 32 | { 33 | name: "065-01", 34 | path: "media/public", 35 | desc: "" 36 | }, 37 | { 38 | name: "example2", 39 | path: "media/internal", 40 | desc: "auto-detect layer example" 41 | }, 42 | { 43 | name: "fs-hi200-01", 44 | path: "media/internal", 45 | desc: "", 46 | useTiles: false, 47 | layerTypes: $scope.layerTypes 48 | }, 49 | { 50 | name: "fd1-01", 51 | path: "media/internal", 52 | desc: "large example", 53 | useTiles: true, 54 | layerTypes: $scope.layerTypes 55 | }, 56 | { 57 | name: "cz-ja101-01", 58 | path: "media/internal", 59 | desc: "", 60 | useTiles: true, 61 | layerTypes: $scope.layerTypes 62 | } 63 | ]; 64 | 65 | $scope.defaultFloorplan = "065-01"; 66 | 67 | //Need a custom CRS so we can set the pixel ratio 68 | L.CRS.NonProjectedFloorPlan = L.extend({}, L.CRS, { 69 | projection: L.Projection.LonLat, 70 | transformation: new L.Transformation(1, 0, -1, 0), 71 | 72 | scaleMult: null, 73 | scale: function (zoom) { 74 | if (this.scaleMult) { 75 | return Math.pow(2, zoom) * this.scaleMult; 76 | } else { 77 | return Math.pow(2, zoom); 78 | } 79 | 80 | }, 81 | setScaleMult: function (scaleMultiplier) { 82 | this.scaleMult = scaleMultiplier; 83 | } 84 | }); 85 | 86 | 87 | 88 | $scope.map = new L.Map('map', { 89 | crs: L.CRS.Simple2, 90 | center: [0.0, 0.0], 91 | worldCopyJump: false 92 | }); 93 | 94 | $scope.map.attributionControl.addAttribution('© SYNCADD Systems, Inc. - Public Demo Data from http://www.physics.ohio-state.edu/fac_engr/flr_plans.htm'); 95 | 96 | $scope.loadFloorPlan = function (name) { 97 | 98 | var i, file, floorPlan; 99 | 100 | for (i = 0; i < $scope.floorplanList.length; i++) { 101 | if ($scope.floorplanList[i].name == name) { 102 | floorPlan = $scope.floorplanList[i]; 103 | } 104 | } 105 | 106 | file = floorPlan.path + $scope.fileSVGPath + name; 107 | 108 | 109 | //file = $scope.fileSVGPath + name; 110 | if (floorPlan.useTiles) { 111 | file = file + $scope.fileOL; 112 | } else { 113 | file = file + $scope.fileExt; 114 | } 115 | d3.html(file, function (data) { 116 | 117 | var svg, viewBox, overlayPane, layerTypes; 118 | 119 | overlayPane = d3.select($scope.map.getPanes().markerPane); //attaching to Leaflets marker pane 120 | 121 | overlayPane.node().appendChild(data); //append the svg 122 | 123 | svg = d3.select("svg"); 124 | 125 | //initialize the floor plan 126 | $scope.fp = new ofp.FloorPlan(overlayPane.node(), floorPlan.layerTypes); 127 | 128 | viewBox = $scope.fp.getViewBox(); 129 | 130 | var minX = viewBox.x, 131 | minY = viewBox.y, 132 | maxX = viewBox.x + viewBox.width, 133 | maxY = viewBox.y + viewBox.height, 134 | centerX = viewBox.x + (viewBox.width / 2), 135 | centerY = viewBox.y + (viewBox.height / 2), 136 | 137 | southWest = new L.LatLng(minY, minX), 138 | northEast = new L.LatLng(maxY, maxX), 139 | bounds = new L.LatLngBounds(southWest, northEast), 140 | center = new L.LatLng(centerY, centerX), 141 | llBounds = new L.LatLngBounds(southWest, center), 142 | ulBounds = new L.LatLngBounds(new L.LatLng(centerY, minX), new L.LatLng(maxY, centerX)), 143 | lrBounds = new L.LatLngBounds(new L.LatLng(minY, centerX), new L.LatLng(centerY, maxX)), 144 | urBounds = new L.LatLngBounds(center, northEast), 145 | xRatio = 1, 146 | yRatio = 1; 147 | 148 | //calculate pixel ratio of the floorplan to the current browser container 149 | var mapSize = $scope.map.getSize(); 150 | 151 | if (mapSize.x < viewBox.width || mapSize.y < viewBox.height){ 152 | xRatio = (viewBox.width / mapSize.x) / 100; 153 | yRatio = (viewBox.height / mapSize.y) / 100; 154 | if (xRatio > yRatio) { 155 | L.CRS.NonProjectedFloorPlan.setScaleMult(xRatio); 156 | } else { 157 | L.CRS.NonProjectedFloorPlan.setScaleMult(yRatio); 158 | } 159 | } 160 | 161 | console.log(viewBox); 162 | 163 | //add background layers 164 | if (floorPlan.useTiles) { 165 | $scope.backgroundLayerGroup = L.layerGroup([ 166 | L.imageOverlay(floorPlan.path + $scope.filePNGPath + name + $scope.fileBGLL, llBounds), 167 | L.imageOverlay(floorPlan.path + $scope.filePNGPath + name + $scope.fileBGUL, ulBounds), 168 | L.imageOverlay(floorPlan.path + $scope.filePNGPath + name + $scope.fileBGLR, lrBounds), 169 | L.imageOverlay(floorPlan.path + $scope.filePNGPath + name + $scope.fileBGUR, urBounds) 170 | ]).addTo($scope.map); 171 | 172 | $scope.backgroundLayerGroup.eachLayer(function (layer) { 173 | //layer.bringToBack(); 174 | }); 175 | } 176 | 177 | //set map bounds 178 | //$scope.map.setMaxBounds(bounds); 179 | $scope.map.fitBounds(bounds); 180 | var mapBounds = $scope.map.getBounds(); 181 | var boundsZoom = $scope.map.getBoundsZoom(bounds, true); 182 | svg.classed('leaflet-zoom-hide', true); 183 | 184 | 185 | var spaces = null; 186 | 187 | if ($scope.fp.spaces) { 188 | spaces = $scope.fp.spaces; 189 | }else if ($scope.fp.NET) { 190 | spaces = $scope.fp.NET; 191 | } 192 | 193 | if (spaces) { 194 | //Mouse hover on spaces 195 | spaces.elements.on("mouseover", 196 | function (d, i) { 197 | 198 | if ($scope.spaceUseDemo) { return; } 199 | 200 | var element = d3.select(this); 201 | element.style('fill', 'white'); 202 | element.transition().duration(300).style('fill', 'red'); 203 | 204 | element.attr("title", this.id); 205 | element.attr("ui-jq", "tooltip"); 206 | 207 | $scope.$apply(function () { 208 | $scope.hoverStatus = element.attr('id'); 209 | }); 210 | 211 | //$(this).tooltip({content: this.id, trigger: 'manual', title: this.id, html:true, animation: true, displayTarget: $('#floorplan')}); 212 | //$(this).popover({content: this.id, trigger: 'manual', title: this.id, html:true, animation: true}); 213 | //$(this).popover('show'); 214 | 215 | }); 216 | 217 | spaces.elements.on("mouseout", 218 | function (d, i) { 219 | 220 | if ($scope.spaceUseDemo) { return; } 221 | 222 | d3.select(this).transition().duration(300).style('fill', 'none'); 223 | //$(this).popover('hide'); 224 | $scope.$apply(function () { 225 | $scope.hoverStatus = ""; 226 | }); 227 | }); 228 | 229 | spaces.elements.on("click", 230 | function (d, i) { 231 | var element, node, space, modalSVG, modalG, bbox, xMin, yMin, viewBoxStr; 232 | 233 | element = d3.select(this); 234 | $scope.$apply(function () { 235 | $scope.modalShown = true; 236 | $scope.selectedSpaceID = element.attr('id'); 237 | }); 238 | 239 | 240 | //clone the space 241 | node = d3.select(this).node(); 242 | space = d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling)); 243 | modalSVG = d3.select('#modal-space-view') 244 | .append('svg'); 245 | modalG = modalSVG.append('g'); 246 | //.attr("width", "100%") 247 | //.attr("height", "100%"); 248 | $(modalG[0][0]) 249 | .append(space[0][0]); 250 | 251 | /*modalSVG.append('svg:path') 252 | .attr('d', line(shapeCoords) + 'Z') 253 | .style('stroke-width', 1) 254 | .style('stroke', 'steelblue') 255 | .style('fill', 'rgba(120, 220, 54, 0.2)');*/ 256 | 257 | bbox = space[0][0].getBBox(); 258 | 259 | xMin = bbox.x.toString(); 260 | yMin = bbox.y.toString(); 261 | 262 | viewBoxStr = xMin + " " + yMin + " " + bbox.width.toString() + " " + bbox.height.toString(); 263 | 264 | //modalSVG.attr("transform", "translate(790,-475)"); 265 | modalSVG.attr("viewBox", viewBoxStr); 266 | 267 | 268 | // Draw Lines 269 | 270 | var line = d3.svg.line() 271 | .x(function (d) { 272 | return d[0]; 273 | }) 274 | .y(function (d) { 275 | return d[1]; 276 | }) 277 | .interpolate('linear'); 278 | 279 | var lineData = []; 280 | 281 | var redrawLine = function () { 282 | var svgLines = modalG.selectAll('path.my-lines') 283 | .data(lineData) 284 | .remove(); 285 | 286 | 287 | svgLines.enter() 288 | .append('path') 289 | .attr('d', line(lineData)) 290 | .attr('class', 'my-lines') 291 | .attr('stroke', 'steelblue') 292 | .attr('stroke-width', 1); 293 | 294 | svgLines.exit() 295 | .remove(); 296 | 297 | //console.debug("lineData", lineData); 298 | }; 299 | 300 | var mouseIsDown = false; 301 | //$('#modal-space-view') 302 | //d3.select('#modal-space-view').select('svg') 303 | 304 | modalSVG.on('mousedown', function () { 305 | mouseIsDown = true; 306 | lineData[0] = d3.mouse(modalSVG[0][0]); 307 | redrawLine(); 308 | }); 309 | modalSVG.on('mouseup', function () { 310 | mouseIsDown = false; 311 | lineData[1] = d3.mouse(modalSVG[0][0]); 312 | redrawLine(); 313 | }); 314 | modalSVG.on('mousemove', function () { 315 | if (mouseIsDown) { 316 | lineData[1] = d3.mouse(modalSVG[0][0]); 317 | redrawLine(); 318 | } 319 | }); 320 | 321 | }); 322 | } 323 | 324 | // Use Leaflet to implement a D3 geographic projection. 325 | function project(val) { 326 | var point = $scope.map.latLngToLayerPoint(new L.LatLng(val.lat, val.lng)); 327 | //var point = $scope.map.project(new L.LatLng(val.lat, val.lng)); 328 | //var point = $scope.map.latLngToContainerPoint(new L.LatLng(val.lat, val.lng)); 329 | return [point.x, point.y]; 330 | } 331 | 332 | // Reposition the SVG to cover the features. 333 | function reset() { 334 | var bottomLeft = project(bounds.getSouthWest()), 335 | topRight = project(bounds.getNorthEast()); 336 | 337 | svg.attr("width", topRight[0] - bottomLeft[0]) 338 | .attr("height", bottomLeft[1] - topRight[1]) 339 | .style("margin-left", bottomLeft[0] + "px") 340 | .style("margin-top", topRight[1] + "px"); 341 | } 342 | 343 | reset(); 344 | $scope.map.on("viewreset", reset); 345 | 346 | 347 | }); 348 | }; //end load floorplan 349 | 350 | $scope.onFloorPlanSelect = function () { 351 | if (this.floorPlanSelection !== "") { 352 | d3.select($scope.map.getPanes().markerPane).select("svg").remove(); 353 | if ($scope.backgroundLayerGroup) { 354 | $scope.backgroundLayerGroup.clearLayers(); 355 | } 356 | $scope.loadFloorPlan(this.floorPlanSelection); 357 | } 358 | 359 | }; 360 | $scope.loadFloorPlan($scope.defaultFloorplan); 361 | 362 | var clearModal = function () { 363 | $scope.modalShown = false; 364 | d3.select('#modal-space-view').select('svg').remove(); 365 | }; 366 | 367 | $scope.onModalCancel = function () { 368 | clearModal(); 369 | }; 370 | 371 | $scope.onModalSave = function () { 372 | clearModal(); 373 | }; 374 | 375 | $scope.onSpaceUseDemo = function () { 376 | var spaces = $scope.fp.spaces.layer.selectAll('polygon'); 377 | if ($scope.spaceUseDemo) { 378 | 379 | spaces.each(function () { 380 | var rand = Math.floor((Math.random() * 3 ) + 1); 381 | var color = 'blue'; 382 | if (rand === 1) { 383 | color = 'red'; 384 | } else if (rand === 2) { 385 | color = 'green'; 386 | } 387 | 388 | d3.select(this).style('fill', 'white'); 389 | d3.select(this).transition().duration(300).style('fill', color); 390 | }); 391 | 392 | 393 | } else { 394 | //turn off the demo 395 | spaces.transition().duration(300).style('fill', 'none'); 396 | } 397 | } 398 | 399 | 400 | }); 401 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Will be compiled down to a single stylesheet with your sass files */ -------------------------------------------------------------------------------- /app/views/floorplan.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
Selection: {{hoverStatus}}
9 |
10 | 13 |
14 |
15 |
16 | 19 | 20 | 21 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ofpViewer", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.4.0", 6 | "angular-resource": "~1.4.0", 7 | "angular-route": "~1.4.0", 8 | "angular-cookies": "~1.4.0", 9 | "angular-sanitize": "~1.4.0 ", 10 | "ofp.js": "https://github.com/openfloorplan/ofp.js.git", 11 | "bootstrap": "~3.3.1", 12 | "d3": "~3.5.3", 13 | "jquery": "~2.1.3", 14 | "angular-ui-select2": "~0.0.5", 15 | "select2": "~3.4.8", 16 | "angular-ui-bootstrap": "~0.12.0", 17 | "es5-shim": "~2.0.11", 18 | "json3": "~3.3.2", 19 | "leaflet": "~0.7.3", 20 | "angular-ui": "~0.4.0" 21 | }, 22 | "devDependencies": { 23 | "angular-mocks": "~1.4.0", 24 | "angular-scenario": "~1.4.0" 25 | }, 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "components" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /convertSVG.js: -------------------------------------------------------------------------------- 1 | /**** 2 | * 3 | * This script is called from Node.js to process a SVG floorplan into: 4 | * 1) a high-res PNG that will be used as a background image 5 | * 2) a new SVG that will contain only overlay/interactive features: 6 | * a) interior spaces 7 | * b) labels 8 | * 9 | * It uses D3.js to process the SVG DOM and PhantomJS to render PNGs 10 | * 11 | * In the future it can be parameterized and turned into a web service 12 | * in order to dynamically change the background image. 13 | * We may also look into using something like Mapnik or TileStache to 14 | * split the high-res into tiles for improved performance 15 | * 16 | * 17 | * Note: I had to pull down this fork of the phantomjs-node module 18 | * https://github.com/sebv/phantomjs-node 19 | * 20 | */ 21 | 22 | 'use strict'; 23 | 24 | var PNGPath = '/app/media/png/', 25 | SVGPath = '/app/media/svg/'; 26 | 27 | 28 | var ConvertSVG = function (filename) { 29 | this.filename = filename; 30 | }; 31 | 32 | ConvertSVG.prototype = { 33 | filename: null, 34 | 35 | /** 36 | * Create PNG 37 | * @param fileSource 38 | * @param fileTarget 39 | * @param width 40 | * @param height 41 | * @param resolutionMultiplier 42 | * @param tileTarget 43 | */ 44 | createPNG: function (fileSource, fileTarget, tileTarget, width, height, resolutionMultiplier) { 45 | //setup phantomJS to render PNG 46 | var phantom = require('phantom'); 47 | phantom.create(function (ph) { 48 | return ph.createPage(function (page) { 49 | //set page dimensions 50 | var pageWidth = width * resolutionMultiplier, 51 | pageHeight = height * resolutionMultiplier; 52 | page.set('viewportSize', {width: pageWidth, height: pageHeight}); 53 | console.log("PNG Dimensions Width:" + pageWidth + " Height:" + pageHeight); 54 | 55 | return page.open(fileSource, function (status) { 56 | if (status !== 'success') { 57 | console.log('Unable to load: ' + fileSource); 58 | ph.exit(); 59 | } else { 60 | page.render(fileTarget); 61 | console.log('Render Complete!'); 62 | ph.exit(); 63 | 64 | var gm = require('gm'), 65 | fs = require('fs'), 66 | 67 | cropHeight = pageHeight / 2, 68 | cropWidth = pageWidth / 2; 69 | 70 | 71 | //upperLeft 72 | gm(fileTarget).crop(cropWidth, cropHeight, 0, 0) 73 | .background('white') 74 | .flatten() 75 | .matteColor('white') 76 | .write(tileTarget + "-bg-ul.jpg", function (err) { 77 | if (!err) { 78 | console.log(' Saved ul tile '); 79 | } else { 80 | console.log(err); 81 | } 82 | }); 83 | 84 | //lowerLeft 85 | gm(fileTarget).crop(cropWidth, cropHeight, 0, cropHeight) 86 | .background('white') 87 | .flatten() 88 | .matteColor('white') 89 | .write(tileTarget + "-bg-ll.jpg", function (err) { 90 | if (!err) { 91 | console.log(' Saved ll tile '); 92 | } else { 93 | console.log(err); 94 | } 95 | }); 96 | 97 | //upperRight 98 | gm(fileTarget).crop(cropWidth, cropHeight, cropWidth, 0) 99 | .background('white') 100 | .flatten() 101 | .matteColor('white') 102 | .write(tileTarget + "-bg-ur.jpg", function (err) { 103 | if (!err) { 104 | console.log(' Saved ur tile '); 105 | } else { 106 | console.log(err); 107 | } 108 | }); 109 | 110 | //lowerRight 111 | gm(fileTarget).crop(cropWidth, cropHeight, cropWidth, cropHeight) 112 | .background('white') 113 | .flatten() 114 | .matteColor('white') 115 | .write(tileTarget + "-bg-lr.jpg", function (err) { 116 | if (!err) { 117 | console.log(' Saved lr tile '); 118 | } else { 119 | console.log(err); 120 | } 121 | }); 122 | 123 | 124 | 125 | } 126 | }); 127 | }); 128 | }); 129 | }, 130 | 131 | /** 132 | * Write data to file 133 | * @param data 134 | * @param fileTarget 135 | */ 136 | writeFile: function (data, fileTarget) { 137 | var fs = require('fs'); 138 | //write to file 139 | fs.writeFileSync(fileTarget, data); 140 | 141 | }, 142 | 143 | //Not using any of this stuff for now 144 | /*mapValue: function (value, minTo, maxTo, minFrom, maxFrom) { 145 | return minTo + (maxTo - minTo) * ((value - minFrom) / (maxFrom - minFrom)); 146 | }, 147 | 148 | projectCADToLatLng: function (latlng, minX, minY, maxX, maxY) { 149 | var targetMinX = -10.0, 150 | targetMinY = -5.0, 151 | targetMaxX = 10.0, 152 | targetMaxY = 5.0, 153 | 154 | mappedLat = this.mapValue(latlng.lat, targetMinY, targetMaxY, minY, maxY), 155 | mappedLng = this.mapValue(latlng.lng, targetMinX, targetMaxX, minX, maxX); 156 | 157 | return {lat: mappedLat, lng: mappedLng}; 158 | 159 | }, 160 | 161 | createGDALTranslateCall: function (viewBox, target) { 162 | //gdal_translate -of GTiff -a_ullr ullat ullon lrlat lelon -a_srs EPSG:4269 input.tif output.tif 163 | // ulx uly lrx lry 164 | var ullaty = Number(viewBox.yMin) + Number(viewBox.height), 165 | ullonx = viewBox.xMin, 166 | lrlaty = viewBox.yMin, 167 | lrlonx = Number(viewBox.xMin) + Number(viewBox.width), 168 | 169 | ul = this.projectCADToLatLng({lat: ullaty, lng: ullonx}, ullonx, lrlaty, lrlonx, ullaty), 170 | lr = this.projectCADToLatLng({lat: lrlaty, lng: lrlonx}, ullonx, lrlaty, lrlonx, ullaty), 171 | 172 | output = "gdal_translate -of GTiff -a_ullr " + 173 | ul.lng.toString() + " " + ul.lat.toString() + " " + lr.lng.toString() + " " + lr.lat.toString() + 174 | " -a_srs EPSG:4326 " + target + " " + target; 175 | 176 | return output; 177 | 178 | 179 | },*/ 180 | 181 | /** 182 | * Convert 183 | */ 184 | convert: function () { 185 | 186 | global.document = require("jsdom").jsdom(""); 187 | global.window = global.document.createWindow(); 188 | 189 | var fs = require('fs'), 190 | ofp = require('openfloorplan'), 191 | 192 | source = __dirname + SVGPath + this.filename + '.svg', 193 | backgroundTarget = __dirname + PNGPath + this.filename + '-bg.png', 194 | tileTarget = __dirname + PNGPath + this.filename, 195 | overlayTarget = __dirname + SVGPath + this.filename + '-ol.svg', 196 | resolutionMultiplier = 15, 197 | convertSVG = this; //need a local scope reference; 198 | 199 | console.log("File: " + backgroundTarget); 200 | 201 | ///////////// 202 | //Overlay 203 | ///////////// 204 | fs.readFile(source, function (err, data) { 205 | if (err) { 206 | throw err; 207 | } 208 | 209 | var fp, viewBox; 210 | 211 | console.log("loaded SVG file"); 212 | 213 | //set body as the SVG data 214 | global.document.body.innerHTML = data; 215 | //get the floorplan 216 | fp = new ofp.FloorPlan(global.document.body); 217 | 218 | //flag everything for removal 219 | fp.svg.selectAll('g [type=level]').classed('tmp-removed', true); 220 | //un-flag the stuff we want to keep 221 | fp.spaces.layer.classed('tmp-removed', false); 222 | fp.dimensionAnnotations.layer.classed('tmp-removed', false); 223 | 224 | //remove everything else 225 | fp.svg.selectAll('.tmp-removed').remove(); 226 | 227 | convertSVG.writeFile(fp.exportSVG(), overlayTarget); 228 | 229 | }); 230 | 231 | ////////////////// 232 | //Background 233 | ///////////////// 234 | //read the source SVG file 235 | fs.readFile(source, function (err, data) { 236 | if (err) { 237 | throw err; 238 | } 239 | 240 | var fp, viewBox; 241 | 242 | console.log("loaded SVG file"); 243 | 244 | //set body as the SVG data 245 | global.document.body.innerHTML = data; 246 | //get the floorplan 247 | fp = new ofp.FloorPlan(global.document.body); 248 | 249 | viewBox = fp.getViewBox(); 250 | 251 | fp.dimensionAnnotations.remove(); 252 | 253 | 254 | 255 | convertSVG.writeFile(fp.exportSVG(), "temp.svg"); 256 | 257 | convertSVG.createPNG("temp.svg", backgroundTarget, tileTarget, viewBox.width, viewBox.height, resolutionMultiplier); 258 | 259 | }); 260 | 261 | 262 | } 263 | 264 | }; 265 | 266 | 267 | 268 | //run it 269 | new ConvertSVG("fs-hi200-01").convert(); 270 | 271 | 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma E2E configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | ANGULAR_SCENARIO, 9 | ANGULAR_SCENARIO_ADAPTER, 10 | 'test/e2e/**/*.js' 11 | ]; 12 | 13 | // list of files to exclude 14 | exclude = []; 15 | 16 | // test results reporter to use 17 | // possible values: dots || progress || growl 18 | reporters = ['progress']; 19 | 20 | // web server port 21 | port = 8080; 22 | 23 | // cli runner port 24 | runnerPort = 9100; 25 | 26 | // enable / disable colors in the output (reporters and logs) 27 | colors = true; 28 | 29 | // level of logging 30 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 31 | logLevel = LOG_INFO; 32 | 33 | // enable / disable watching file and executing tests whenever any file changes 34 | autoWatch = false; 35 | 36 | // Start these browsers, currently available: 37 | // - Chrome 38 | // - ChromeCanary 39 | // - Firefox 40 | // - Opera 41 | // - Safari (only Mac) 42 | // - PhantomJS 43 | // - IE (only Windows) 44 | browsers = ['Chrome']; 45 | 46 | // If browser does not capture in given timeout [ms], kill it 47 | captureTimeout = 5000; 48 | 49 | // Continuous Integration mode 50 | // if true, it capture browsers, run tests and exit 51 | singleRun = false; 52 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | JASMINE, 9 | JASMINE_ADAPTER, 10 | 'app/components/angular/angular.js', 11 | 'app/components/angular-mocks/angular-mocks.js', 12 | 'app/scripts/*.js', 13 | 'app/scripts/**/*.js', 14 | 'test/mock/**/*.js', 15 | 'test/spec/**/*.js' 16 | ]; 17 | 18 | // list of files to exclude 19 | exclude = []; 20 | 21 | // test results reporter to use 22 | // possible values: dots || progress || growl 23 | reporters = ['progress']; 24 | 25 | // web server port 26 | port = 8080; 27 | 28 | // cli runner port 29 | runnerPort = 9100; 30 | 31 | // enable / disable colors in the output (reporters and logs) 32 | colors = true; 33 | 34 | // level of logging 35 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 36 | logLevel = LOG_INFO; 37 | 38 | // enable / disable watching file and executing tests whenever any file changes 39 | autoWatch = false; 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari (only Mac) 47 | // - PhantomJS 48 | // - IE (only Windows) 49 | browsers = ['Chrome']; 50 | 51 | // If browser does not capture in given timeout [ms], kill it 52 | captureTimeout = 5000; 53 | 54 | // Continuous Integration mode 55 | // if true, it capture browsers, run tests and exit 56 | singleRun = false; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "SYNCADD Systems, Inc.", 3 | "name": "ofp-viewer", 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "compass": "^0.1.0", 7 | "d3": "3.5.3", 8 | "gm": "1.8.1", 9 | "jsdom": "0.3.4", 10 | "ofp.js": "git://github.com/openfloorplan/ofp.js.git", 11 | "phantom": "0.3.5" 12 | }, 13 | "devDependencies": { 14 | "grunt": "~0.4.1", 15 | "grunt-contrib-copy": "~0.4.0", 16 | "grunt-contrib-concat": "~0.1.3", 17 | "grunt-contrib-coffee": "~0.6.4", 18 | "grunt-contrib-uglify": "~0.2.0", 19 | "grunt-contrib-compass": "~0.1.3", 20 | "grunt-contrib-jshint": "~0.3.0", 21 | "grunt-contrib-cssmin": "~0.5.0", 22 | "grunt-contrib-connect": "~0.2.0", 23 | "grunt-contrib-clean": "~0.4.0", 24 | "grunt-contrib-htmlmin": "~0.1.1", 25 | "grunt-contrib-imagemin": "~0.1.2", 26 | "grunt-contrib-livereload": "~0.1.2", 27 | "grunt-bower-requirejs": "~0.4.1", 28 | "grunt-usemin": "~0.1.10", 29 | "grunt-regarde": "~0.1.1", 30 | "grunt-rev": "~0.1.0", 31 | "grunt-karma": "~0.3.0", 32 | "grunt-open": "~0.2.0", 33 | "matchdep": "~0.1.1", 34 | "grunt-google-cdn": "~0.1.1", 35 | "grunt-ngmin": "~0.0.2" 36 | }, 37 | "engines": { 38 | "node": ">=0.8.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/spec/controllers/floorplan.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: FloorplanCtrl', function() { 4 | 5 | // load the controller's module 6 | beforeEach(module('floorplanApp')); 7 | 8 | var FloorplanCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function($controller) { 13 | scope = {}; 14 | FloorplanCtrl = $controller('FloorplanCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function() { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function() { 4 | 5 | // load the controller's module 6 | beforeEach(module('floorplanApp')); 7 | 8 | var MainCtrl, 9 | FloorplanCtrl, 10 | scope; 11 | 12 | // Initialize the controller and a mock scope 13 | beforeEach(inject(function ($controller, $rootScope) { 14 | scope = $rootScope.$new(); 15 | MainCtrl = $controller('MainCtrl', { 16 | $scope: scope 17 | }); 18 | FloorplanCtrl = $controller('FloorplanCtrl', { 19 | $scope: scope 20 | }); 21 | })); 22 | 23 | it('should attach a list of awesomeThings to the scope', function () { 24 | expect(scope.awesomeThings.length).toBe(3); 25 | }); 26 | }); 27 | --------------------------------------------------------------------------------