├── src ├── main │ ├── webapp │ │ ├── META-INF │ │ │ └── MANIFEST.MF │ │ ├── index.html │ │ └── WEB-INF │ │ │ ├── appengine-web.xml │ │ │ └── web.xml │ ├── sproutcore │ │ ├── apps │ │ │ └── oeffi_npc │ │ │ │ ├── resources │ │ │ │ ├── templates │ │ │ │ │ ├── network_plan.handlebars │ │ │ │ │ ├── oeffi_npc.handlebars │ │ │ │ │ └── entry.handlebars │ │ │ │ ├── images │ │ │ │ │ ├── linz.png │ │ │ │ │ ├── linz_bw.png │ │ │ │ │ ├── bonn_schnellverkehr.png │ │ │ │ │ └── bonn_schnellverkehr_bw.png │ │ │ │ ├── stylesheets │ │ │ │ │ └── oeffi_npc.css │ │ │ │ └── main_page.js │ │ │ │ ├── statechart │ │ │ │ ├── statechart.js │ │ │ │ ├── loading_state.js │ │ │ │ ├── show_network_plans_state.js │ │ │ │ └── show_network_state_state.js │ │ │ │ ├── models │ │ │ │ ├── record_status_mixin.js │ │ │ │ ├── network_plan_model.js │ │ │ │ └── network_plan_entry_model.js │ │ │ │ ├── oeffi_npc.js │ │ │ │ ├── controllers │ │ │ │ ├── network_plan_controller.js │ │ │ │ ├── network_plans_controller.js │ │ │ │ ├── network_plan_entry_controller.js │ │ │ │ ├── network_plan_entries_controller.js │ │ │ │ └── network_plan_view_controller.js │ │ │ │ ├── tests │ │ │ │ └── controllers │ │ │ │ │ ├── network_plan_test.js │ │ │ │ │ ├── network_plans_test.js │ │ │ │ │ ├── network_plan_entry_test.js │ │ │ │ │ └── network_plan_entries_test.js │ │ │ │ ├── views │ │ │ │ ├── image_view.js │ │ │ │ ├── magnifier_view.js │ │ │ │ └── network_plan_view.js │ │ │ │ ├── fixtures │ │ │ │ ├── network_plan_fixtures.js │ │ │ │ └── network_plan_entry_fixtures.js │ │ │ │ └── data_sources │ │ │ │ └── rest_data_source.js │ │ ├── README │ │ └── Buildfile │ ├── resources │ │ ├── com │ │ │ └── pangratz │ │ │ │ └── oeffinpc │ │ │ │ └── template.tfl │ │ ├── META-INF │ │ │ ├── persistence.xml │ │ │ └── jdoconfig.xml │ │ ├── logging.properties │ │ └── log4j.properties │ └── java │ │ └── com │ │ └── pangratz │ │ └── oeffinpc │ │ ├── model │ │ ├── PMF.java │ │ ├── NetworkPlan.java │ │ ├── NetworkPlanEntry.java │ │ └── ModelUtils.java │ │ ├── rest │ │ ├── OeffiNetworkPlanCuratorApplication.java │ │ ├── NetworkPlansResource.java │ │ ├── OeffiNpcServerResource.java │ │ ├── NetworkPlanEntryResource.java │ │ ├── NetworkPlanEntriesResource.java │ │ └── NetworkPlanResource.java │ │ └── util │ │ └── CsvUtils.java └── test │ ├── java │ └── com │ │ └── pangratz │ │ └── oeffinpc │ │ ├── util │ │ └── CsvUtilTest.java │ │ ├── rest │ │ └── PostFileTest.java │ │ └── model │ │ └── ModelUtilsTest.java │ └── resources │ ├── bonn_schnellverkehr.csv │ └── berlin_tram.csv ├── .gitignore ├── README.md ├── run_server.js ├── run_dev_server.js ├── gae-deploy.sh └── pom.xml /src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/templates/network_plan.handlebars: -------------------------------------------------------------------------------- 1 | {{networkId}} -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

öffi Networkplan Curator

5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target 3 | 4 | .project 5 | .classpath 6 | .settings 7 | 8 | src/main/sproutcore/tmp 9 | 10 | node_modules/ -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/images/linz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/oeffi-networkplan-curator/dev/src/main/sproutcore/apps/oeffi_npc/resources/images/linz.png -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/images/linz_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/oeffi-networkplan-curator/dev/src/main/sproutcore/apps/oeffi_npc/resources/images/linz_bw.png -------------------------------------------------------------------------------- /src/main/resources/com/pangratz/oeffinpc/template.tfl: -------------------------------------------------------------------------------- 1 | <#list entries as planEntry> 2 | ${networkPlan.networkId}|${planEntry.stationId}|${planEntry.name}|${networkPlan.planId}|${planEntry.x?c}|${planEntry.y?c} 3 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/images/bonn_schnellverkehr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/oeffi-networkplan-curator/dev/src/main/sproutcore/apps/oeffi_npc/resources/images/bonn_schnellverkehr.png -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/images/bonn_schnellverkehr_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/oeffi-networkplan-curator/dev/src/main/sproutcore/apps/oeffi_npc/resources/images/bonn_schnellverkehr_bw.png -------------------------------------------------------------------------------- /src/main/sproutcore/README: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | Project: OeffiNpc 3 | Copyright: @2011 My Company, Inc. 4 | ========================================================================== 5 | 6 | TODO: Describe Your Project 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This webapp lets you create and edit the csv files of networkplans which are needed by [Öffi](http://oeffi.schildbach.de/) to make the network plans interactive. The idea for this webapp was born by [this](http://twitter.com/oeffi/status/83913809521164289) Tweet. -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/stylesheets/oeffi_npc.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | * Project: OeffiNpc 3 | * Copyright: @2011 My Company, Inc. 4 | * ========================================================================== 5 | */ 6 | 7 | /*.sc-view { 8 | position: relative; 9 | overflow: visible; 10 | }*/ 11 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/statechart/statechart.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | 3 | OeffiNpc.statechart = SC.Statechart.create({ 4 | 5 | trace: YES, 6 | initialState: 'loading', 7 | 8 | loading: SC.State.plugin('OeffiNpc.LoadingState'), 9 | showNetworkPlans: SC.State.plugin('OeffiNpc.ShowNetworkPlansState'), 10 | showNetworkPlan: SC.State.plugin('OeffiNpc.ShowNetworkPlanState') 11 | 12 | }); -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/templates/oeffi_npc.handlebars: -------------------------------------------------------------------------------- 1 | {{#view SC.LabelView valueBinding="OeffiNpc.networkPlansController.numberOfNetworkPlans"}}{{/view}} 2 | 3 | {{#view SC.LabelView valueBinding="OeffiNpc.networkPlanController"}}{{/view}} 4 | 5 | {{#view SC.LabelView valueBinding="OeffiNpc.networkPlanEntriesController.length"}}{{/view}} 6 | 7 | {{#view SC.ListView contentBinding="OeffiNpc.networkPlansController" }}{{/view}} -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/models/record_status_mixin.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | /** 3 | 4 | A mixin which adds a `recordStatusString` method to a SC.Record, and which returns 5 | a readable representation of a record's status. This is quite handy during developing. 6 | 7 | */ 8 | OeffiNpc.RecordStatusMixin = { 9 | 10 | recordStatusString: function(){ 11 | return this.statusString(); 12 | }.property('status') 13 | 14 | }; -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/templates/entry.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Name: {{entry.name}}
4 |
5 |
6 |
_id: {{entry._id}}
7 |
8 |
9 |
Status: {{entry.status}}
10 |
11 |
12 |
Id: {{entry.stationId}}
13 |
14 |
15 |
X: {{entry.x}}
16 |
17 |
18 |
Y: {{entry.y}}
19 |
20 |
-------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/statechart/loading_state.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | 3 | OeffiNpc.LoadingState = SC.State.extend({ 4 | 5 | enterState: function() { 6 | var query = SC.Query.local(OeffiNpc.NetworkPlan, { 7 | orderBy: 'networkId ASC' 8 | }); 9 | var networkPlans = OeffiNpc.store.find(query); 10 | OeffiNpc.networkPlansController.set('content', networkPlans); 11 | 12 | this.gotoState('showNetworkPlans'); 13 | } 14 | 15 | }); -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/model/PMF.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.model; 2 | 3 | import javax.jdo.JDOHelper; 4 | import javax.jdo.PersistenceManagerFactory; 5 | 6 | public final class PMF { 7 | private static final PersistenceManagerFactory pmfInstance = JDOHelper 8 | .getPersistenceManagerFactory("transactions-optional"); 9 | 10 | public static PersistenceManagerFactory get() { 11 | return pmfInstance; 12 | } 13 | 14 | private PMF() { 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/sproutcore/Buildfile: -------------------------------------------------------------------------------- 1 | # ========================================================================== 2 | # Project: OeffiNpc 3 | # Copyright: @2011 My Company, Inc. 4 | # ========================================================================== 5 | 6 | # Add initial buildfile information here 7 | config :all, 8 | :required => [:sproutcore, "sproutcore/statechart"] 9 | 10 | proxy '/static', :to => 'oeffinpc.appspot.com' 11 | proxy '/', :to => 'localhost:3000' 12 | 13 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/oeffi_npc.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | OeffiNpc = SC.Application.create({ 8 | store: SC.Store.create({commitRecordsAutomatically: YES}).from('OeffiNpc.RestDataSource') 9 | }); 10 | 11 | SC.ready(function() { 12 | OeffiNpc.statechart.initStatechart(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | oeffinpc 6 | test 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/controllers/network_plan_controller.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanController 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Controller Here) 10 | 11 | @extends SC.Object 12 | */ 13 | OeffiNpc.networkPlanController = SC.ObjectController.create( 14 | /** @scope OeffiNpc.networkPlanController.prototype */ { 15 | 16 | contentBinding: 'OeffiNpc.networkPlansController.selection' 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/tests/controllers/network_plan_test.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanController Unit Test 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc module test ok equals same stop start */ 6 | 7 | module("OeffiNpc.networkPlanController"); 8 | 9 | // TODO: Replace with real unit test for OeffiNpc.networkPlanController 10 | test("test description", function() { 11 | var expected = "test"; 12 | var result = "test"; 13 | equals(result, expected, "test should equal test"); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/models/network_plan_model.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.NetworkPlan 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document your Model here) 10 | 11 | @extends SC.Record 12 | @version 0.1 13 | */ 14 | OeffiNpc.NetworkPlan = SC.Record.extend( 15 | /** @scope OeffiNpc.NetworkPlan.prototype */ { 16 | 17 | primaryKey: 'key', 18 | networkId: SC.Record.attr(String), 19 | planId: SC.Record.attr(String), 20 | imageUrl: SC.Record.attr(String) 21 | 22 | }) ; 23 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/tests/controllers/network_plans_test.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlansController Unit Test 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc module test ok equals same stop start */ 6 | 7 | module("OeffiNpc.networkPlansController"); 8 | 9 | // TODO: Replace with real unit test for OeffiNpc.networkPlansController 10 | test("test description", function() { 11 | var expected = "test"; 12 | var result = "test"; 13 | equals(result, expected, "test should equal test"); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/tests/controllers/network_plan_entry_test.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanEntryController Unit Test 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc module test ok equals same stop start */ 6 | 7 | module("OeffiNpc.networkPlanEntryController"); 8 | 9 | // TODO: Replace with real unit test for OeffiNpc.networkPlanEntryController 10 | test("test description", function() { 11 | var expected = "test"; 12 | var result = "test"; 13 | equals(result, expected, "test should equal test"); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/tests/controllers/network_plan_entries_test.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanEntriesController Unit Test 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc module test ok equals same stop start */ 6 | 7 | module("OeffiNpc.networkPlanEntriesController"); 8 | 9 | // TODO: Replace with real unit test for OeffiNpc.networkPlanEntriesController 10 | test("test description", function() { 11 | var expected = "test"; 12 | var result = "test"; 13 | equals(result, expected, "test should equal test"); 14 | }); 15 | 16 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/views/image_view.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.ImageView 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your View Here) 10 | */ 11 | OeffiNpc.ImageViewMixin = { 12 | 13 | scale: 1.0, 14 | 15 | _imageChanged: function(){ 16 | var image = this.get('image'); 17 | if (image) { 18 | var layout = { 19 | width: this.scale * image.width, 20 | height: this.scale * image.height 21 | }; 22 | this.set('layout', layout); 23 | } 24 | }.observes('image') 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/controllers/network_plans_controller.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlansController 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Controller Here) 10 | 11 | @extends SC.Object 12 | */ 13 | OeffiNpc.networkPlansController = SC.ArrayController.create( 14 | /** @scope OeffiNpc.networkPlansController.prototype */ { 15 | 16 | content: [], 17 | allowsMultipleSelection: YES, 18 | 19 | numberOfNetworkPlans: function(){ 20 | return this.get('length') + ' network plans'; 21 | }.property('length') 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/OeffiNetworkPlanCuratorApplication.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import org.restlet.Application; 4 | import org.restlet.Restlet; 5 | import org.restlet.routing.Router; 6 | 7 | public class OeffiNetworkPlanCuratorApplication extends Application { 8 | 9 | @Override 10 | public Restlet createInboundRoot() { 11 | Router router = new Router(getContext()); 12 | router.attach("/networkplans", NetworkPlansResource.class); 13 | router.attach("/networkplans/{networkPlanId}", NetworkPlanResource.class); 14 | router.attach("/networkplans/{networkPlanId}/_entries", NetworkPlanEntriesResource.class); 15 | router.attach("/networkplanentries/{stationId}", NetworkPlanEntryResource.class); 16 | return router; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/controllers/network_plan_entry_controller.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanEntryController 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Controller Here) 10 | 11 | @extends SC.Object 12 | */ 13 | OeffiNpc.networkPlanEntryController = SC.ObjectController.create( 14 | /** @scope OeffiNpc.networkPlanEntryController.prototype */ { 15 | 16 | contentBinding: 'OeffiNpc.networkPlanEntriesController.selection', 17 | 18 | networkEntryDidChange: function(){ 19 | OeffiNpc.statechart.sendEvent('networkEntrySelected'); 20 | }.observes('content') 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/controllers/network_plan_entries_controller.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanEntriesController 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Controller Here) 10 | 11 | @extends SC.Object 12 | */ 13 | OeffiNpc.networkPlanEntriesController = SC.ArrayController.create( 14 | /** @scope OeffiNpc.networkPlanEntriesController.prototype */ { 15 | 16 | isEditable: YES, 17 | destroyOnRemoval: YES, 18 | allowsMultipleSelection: NO, 19 | content: [], 20 | 21 | numberOfNetworkPlanEntries: function(){ 22 | return this.get('length') + ' entries'; 23 | }.property('length') 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/models/network_plan_entry_model.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.NetworkPlanEntry 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document your Model here) 10 | 11 | @extends SC.Record 12 | @version 0.1 13 | */ 14 | sc_require('models/record_status_mixin'); 15 | 16 | OeffiNpc.NetworkPlanEntry = SC.Record.extend(OeffiNpc.RecordStatusMixin, 17 | /** @scope OeffiNpc.NetworkPlanEntry.prototype */ { 18 | 19 | primaryKey: 'key', 20 | stationId: SC.Record.attr(String), 21 | name: SC.Record.attr(String), 22 | x: SC.Record.attr(Number), 23 | y: SC.Record.attr(Number), 24 | networkPlanKey: SC.Record.attr(String) 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/statechart/show_network_plans_state.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | 3 | OeffiNpc.ShowNetworkPlansState = SC.State.extend({ 4 | 5 | enterState: function(){ 6 | this.set('pane', OeffiNpc.getPath('mainPage.listNetworkPlansPane').append()); 7 | }, 8 | 9 | exitState: function(){ 10 | this.get('pane').remove(); 11 | }, 12 | 13 | networkPlanSelected: function() { 14 | var key = OeffiNpc.networkPlanController.get('id'); 15 | if (key) { 16 | var query = SC.Query.local(OeffiNpc.NetworkPlanEntry, { 17 | orderBy: 'name', 18 | conditions: "networkPlanKey = '" + key + "'", 19 | parameters: { 20 | networkPlanKey: key 21 | } 22 | }); 23 | var entries = OeffiNpc.store.find(query); 24 | OeffiNpc.networkPlanEntriesController.set('content', entries); 25 | 26 | this.gotoState('showNetworkPlan'); 27 | } 28 | } 29 | 30 | }); -------------------------------------------------------------------------------- /src/test/java/com/pangratz/oeffinpc/util/CsvUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.util; 2 | 3 | import java.io.InputStream; 4 | import java.util.List; 5 | 6 | import junit.framework.TestCase; 7 | 8 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 9 | 10 | public class CsvUtilTest extends TestCase { 11 | 12 | private CsvUtils csvUtils; 13 | 14 | public void testReadCsv() { 15 | InputStream inputStream = CsvUtilTest.class.getResourceAsStream("/bonn_schnellverkehr.csv"); 16 | List entries = this.csvUtils.readCsv(inputStream); 17 | assertNotNull(entries); 18 | assertTrue(entries.size() > 0); 19 | } 20 | 21 | @Override 22 | protected void setUp() throws Exception { 23 | super.setUp(); 24 | this.csvUtils = CsvUtils.getInstance(); 25 | } 26 | 27 | @Override 28 | protected void tearDown() throws Exception { 29 | super.tearDown(); 30 | this.csvUtils = null; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/fixtures/network_plan_fixtures.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.NetworkPlan Fixtures 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | sc_require('models/network_plan_model'); 8 | 9 | OeffiNpc.NetworkPlan.FIXTURES = [ 10 | 11 | { 12 | guid: 'linz', 13 | networkId: 'linz', 14 | planId: 'linz', 15 | imageUrl: 'http://oeffi.schildbach.de/plans/linz.png', 16 | imageWidth: 1114, 17 | imageHeight: 1618, 18 | entries: ['schumpeterstrasse', 'taubenmarkt'] 19 | }, 20 | 21 | { 22 | guid: 'bonn', 23 | networkId: 'bonn', 24 | planId: 'bonn', 25 | imageUrl: 'http://oeffi.schildbach.de/plans/bonn_schnellverkehr.png', 26 | imageWidth: 2338, 27 | imageHeight: 1653, 28 | entries: ['landesmuseum', 'innsbruck_hauptbahnhof'] 29 | } 30 | 31 | ]; 32 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/jdoconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/controllers/network_plan_view_controller.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.networkPlanViewController 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Controller Here) 10 | 11 | @extends SC.Object 12 | */ 13 | OeffiNpc.networkPlanViewController = SC.ObjectController.create( 14 | /** @scope OeffiNpc.networkPlanViewController.prototype */ { 15 | 16 | cursorPosition: { 17 | x: undefined, 18 | y: undefined 19 | }, 20 | zoom: NO, 21 | zoomScale: 2.0, 22 | 23 | scrollPosition: { 24 | x: undefined, 25 | y: undefined 26 | }, 27 | 28 | cursorPositionString: function() { 29 | var pos = this.get('cursorPosition'); 30 | if (pos) { 31 | return '%@1/%@2'.fmt(pos.x, pos.y); 32 | } 33 | return undefined; 34 | }.property('cursorPosition') 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/views/magnifier_view.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.MagnifierView 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your View Here) 10 | 11 | @extends SC.View 12 | */ 13 | OeffiNpc.MagnifierView = SC.ImageView.extend(OeffiNpc.ImageViewMixin, 14 | /** @scope OeffiNpc.MagnifierView.prototype */ { 15 | 16 | scale: 0.5, 17 | 18 | positionChanged: function(){ 19 | var pos = this.get('position'); 20 | if (pos) { 21 | var scrollView = this.get('parentView').get('parentView'); 22 | var parentLayout = scrollView.get('layout'); 23 | var scrollTo = { 24 | x: this.scale * pos.x - (parentLayout.width / 2), 25 | y: this.scale * pos.y - (parentLayout.height / 2) 26 | }; 27 | scrollView.scrollTo(scrollTo); 28 | } 29 | }.observes('position') 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | RestletServlet 9 | org.restlet.ext.servlet.ServerServlet 10 | 11 | org.restlet.application 12 | com.pangratz.oeffinpc.rest.OeffiNetworkPlanCuratorApplication 13 | 14 | 15 | 16 | 17 | 18 | RestletServlet 19 | /* 20 | 21 | 22 | 23 | static/oeffi_npc/en/build/index.html 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # A default java.util.logging configuration. 2 | # (All App Engine logging is through java.util.logging by default). 3 | # 4 | # To use this configuration, copy it into your application's WEB-INF 5 | # folder and add the following to your appengine-web.xml: 6 | # 7 | # 8 | # 9 | # 10 | # 11 | 12 | # Set the default logging level for all loggers to WARNING 13 | .level = WARNING 14 | 15 | # Set the default logging level for ORM, specifically, to WARNING 16 | DataNucleus.JDO.level=WARNING 17 | DataNucleus.Persistence.level=WARNING 18 | DataNucleus.Cache.level=WARNING 19 | DataNucleus.MetaData.level=WARNING 20 | DataNucleus.General.level=WARNING 21 | DataNucleus.Utility.level=WARNING 22 | DataNucleus.Transaction.level=WARNING 23 | DataNucleus.Datastore.level=WARNING 24 | DataNucleus.ClassLoading.level=WARNING 25 | DataNucleus.Plugin.level=WARNING 26 | DataNucleus.ValueGeneration.level=WARNING 27 | DataNucleus.Enhancer.level=WARNING 28 | DataNucleus.SchemaTool.level=WARNING 29 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/fixtures/network_plan_entry_fixtures.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.NetworkplanEntry Fixtures 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | sc_require('models/network_plan_entry_model'); 8 | 9 | OeffiNpc.NetworkPlanEntry.FIXTURES = [ 10 | 11 | { 12 | guid: 'schumpeterstrasse', 13 | stationId: 491115, 14 | name: 'Linz/Donau Schumpeterstrasse', 15 | x: 200, 16 | y: 50, 17 | networkPlan: 'linz' 18 | }, 19 | 20 | { 21 | guid: 'taubenmarkt', 22 | stationId: 491106, 23 | name: 'Linz/Donau Taubenmarkt', 24 | x: 50, 25 | y: 150, 26 | networkPlan: 'linz' 27 | }, 28 | 29 | { 30 | guid: 'landesmuseum', 31 | stationId: 123, 32 | name: 'Innsbruck Landesmuseum', 33 | x: 40, 34 | y: 50, 35 | networkPlan: 'innsbruck' 36 | }, 37 | 38 | { 39 | guid: 'innsbruck_hauptbahnhof', 40 | stationId: 456, 41 | name: 'Innsbruck Hauptbahnhof', 42 | x: 10, 43 | y: 15, 44 | networkPlan: 'innsbruck' 45 | } 46 | 47 | ]; 48 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # A default log4j configuration for log4j users. 2 | # 3 | # To use this configuration, deploy it into your application's WEB-INF/classes 4 | # directory. You are also encouraged to edit it as you like. 5 | 6 | # Configure the console as our one appender 7 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n 10 | 11 | # tighten logging on the DataNucleus Categories 12 | log4j.category.DataNucleus.JDO=WARN, A1 13 | log4j.category.DataNucleus.Persistence=WARN, A1 14 | log4j.category.DataNucleus.Cache=WARN, A1 15 | log4j.category.DataNucleus.MetaData=WARN, A1 16 | log4j.category.DataNucleus.General=WARN, A1 17 | log4j.category.DataNucleus.Utility=WARN, A1 18 | log4j.category.DataNucleus.Transaction=WARN, A1 19 | log4j.category.DataNucleus.Datastore=WARN, A1 20 | log4j.category.DataNucleus.ClassLoading=WARN, A1 21 | log4j.category.DataNucleus.Plugin=WARN, A1 22 | log4j.category.DataNucleus.ValueGeneration=WARN, A1 23 | log4j.category.DataNucleus.Enhancer=WARN, A1 24 | log4j.category.DataNucleus.SchemaTool=WARN, A1 25 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/util/CsvUtils.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.util; 2 | 3 | import java.io.InputStream; 4 | import java.io.InputStreamReader; 5 | import java.io.Reader; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | import au.com.bytecode.opencsv.CSVReader; 10 | 11 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 12 | 13 | public class CsvUtils { 14 | 15 | private static final CsvUtils instance = new CsvUtils(); 16 | 17 | public static CsvUtils getInstance() { 18 | return instance; 19 | } 20 | 21 | public List readCsv(InputStream inStream) { 22 | Reader reader = new InputStreamReader(inStream); 23 | CSVReader csvReader = new CSVReader(reader, '|'); 24 | String[] next = null; 25 | List entries = new LinkedList(); 26 | try { 27 | while ((next = csvReader.readNext()) != null) { 28 | if (next != null && next.length == 6 && !next[0].startsWith("#")) { 29 | NetworkPlanEntry entry = new NetworkPlanEntry(); 30 | entry.setStationId(next[1]); 31 | entry.setName(next[2]); 32 | entry.setX(Integer.parseInt(next[4])); 33 | entry.setY(Integer.parseInt(next[5])); 34 | entries.add(entry); 35 | } 36 | } 37 | } catch (Exception e) { 38 | throw new IllegalStateException(e); 39 | } 40 | return entries; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/model/NetworkPlan.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.model; 2 | 3 | import javax.jdo.annotations.IdGeneratorStrategy; 4 | import javax.jdo.annotations.PersistenceCapable; 5 | import javax.jdo.annotations.Persistent; 6 | import javax.jdo.annotations.PrimaryKey; 7 | 8 | import org.json.JSONObject; 9 | import org.json.JSONString; 10 | 11 | @PersistenceCapable(detachable = "true") 12 | public class NetworkPlan implements JSONString { 13 | 14 | @PrimaryKey 15 | @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) 16 | private Long key; 17 | 18 | @Persistent 19 | private String networkId; 20 | 21 | @Persistent 22 | private String planId; 23 | 24 | @Persistent 25 | private String imageUrl; 26 | 27 | public String getImageUrl() { 28 | return imageUrl; 29 | } 30 | 31 | public Long getKey() { 32 | return key; 33 | } 34 | 35 | public String getNetworkId() { 36 | return networkId; 37 | } 38 | 39 | public String getPlanId() { 40 | return planId; 41 | } 42 | 43 | public void setImageUrl(String imageUrl) { 44 | this.imageUrl = imageUrl; 45 | } 46 | 47 | public void setKey(Long key) { 48 | this.key = key; 49 | } 50 | 51 | public void setNetworkId(String networkId) { 52 | this.networkId = networkId; 53 | } 54 | 55 | public void setPlanId(String planId) { 56 | this.planId = planId; 57 | } 58 | 59 | @Override 60 | public String toJSONString() { 61 | return new JSONObject(this).toString(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/statechart/show_network_state_state.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | 3 | OeffiNpc.ShowNetworkPlanState = SC.State.extend({ 4 | 5 | enterState: function(){ 6 | this.set('pane', OeffiNpc.getPath('mainPage.mainPane').append()); 7 | }, 8 | 9 | exitState: function(){ 10 | this.get('pane').remove(); 11 | }, 12 | 13 | backToNetworkPlans: function(){ 14 | this.gotoState('showNetworkPlans'); 15 | }, 16 | 17 | clickedOnNetworkPlan: function(point) { 18 | OeffiNpc.networkPlanEntryController.set('x', point.x); 19 | OeffiNpc.networkPlanEntryController.set('y', point.y); 20 | }, 21 | 22 | networkEntrySelected: function(){ 23 | var x = OeffiNpc.networkPlanEntryController.get('x'); 24 | var y = OeffiNpc.networkPlanEntryController.get('y'); 25 | OeffiNpc.networkPlanViewController.set('scrollPosition', { 26 | x: x, 27 | y: y 28 | }); 29 | }, 30 | 31 | addEntry: function(){ 32 | var networkPlanId = OeffiNpc.networkPlanController.get('id'); 33 | var newEntry = OeffiNpc.store.createRecord(OeffiNpc.NetworkPlanEntry, { 34 | name: 'new entry', 35 | stationId: '', 36 | networkPlanKey: networkPlanId 37 | }); 38 | }, 39 | 40 | removeEntry: function(){ 41 | OeffiNpc.networkPlanEntryController.destroy(); 42 | }, 43 | 44 | zPressed: function(){ 45 | var zoom = OeffiNpc.networkPlanViewController.get('zoom'); 46 | OeffiNpc.networkPlanViewController.set('zoom', !zoom); 47 | }, 48 | 49 | numberPressed: function(nr) { 50 | var scale = nr * 0.5; 51 | OeffiNpc.networkPlanViewController.set('zoomScale', scale); 52 | } 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /src/test/java/com/pangratz/oeffinpc/rest/PostFileTest.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.InputStream; 5 | 6 | import junit.framework.TestCase; 7 | 8 | import org.apache.commons.httpclient.HttpClient; 9 | import org.apache.commons.httpclient.methods.PostMethod; 10 | import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource; 11 | import org.apache.commons.httpclient.methods.multipart.FilePart; 12 | import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; 13 | import org.apache.commons.httpclient.methods.multipart.Part; 14 | import org.apache.commons.httpclient.methods.multipart.PartSource; 15 | 16 | public class PostFileTest extends TestCase { 17 | 18 | public void testUploadFile() { 19 | try { 20 | HttpClient httpClient = new HttpClient(); 21 | String csvFileName = "/berlin_tram.csv"; 22 | InputStream stream = PostFileTest.class.getResourceAsStream(csvFileName); 23 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 24 | byte[] buff = new byte[1024]; 25 | while (stream.read(buff) != -1) { 26 | baos.write(buff); 27 | } 28 | PartSource fileSource = new ByteArrayPartSource(csvFileName, baos.toByteArray()); 29 | 30 | PostMethod post = new PostMethod("http://localhost:8080/networkplans/3011"); 31 | FilePart filePart = new FilePart(csvFileName, fileSource); 32 | filePart.setTransferEncoding("utf-8"); 33 | Part[] parts = { filePart }; 34 | post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams())); 35 | 36 | httpClient.executeMethod(post); 37 | byte[] responseBody = post.getResponseBody(); 38 | System.out.println(new String(responseBody)); 39 | } catch (Exception ex) { 40 | ex.printStackTrace(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/model/NetworkPlanEntry.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.model; 2 | 3 | import javax.jdo.annotations.IdGeneratorStrategy; 4 | import javax.jdo.annotations.PersistenceCapable; 5 | import javax.jdo.annotations.Persistent; 6 | import javax.jdo.annotations.PrimaryKey; 7 | 8 | import org.json.JSONObject; 9 | import org.json.JSONString; 10 | 11 | @PersistenceCapable(detachable = "true") 12 | public class NetworkPlanEntry implements JSONString { 13 | 14 | @PrimaryKey 15 | @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) 16 | private Long key; 17 | 18 | @Persistent 19 | private Long networkPlanKey; 20 | 21 | @Persistent 22 | private String stationId; 23 | 24 | @Persistent 25 | private String name; 26 | 27 | @Persistent 28 | private int x; 29 | 30 | @Persistent 31 | private int y; 32 | 33 | public Long getKey() { 34 | return key; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public Long getNetworkPlanKey() { 42 | return networkPlanKey; 43 | } 44 | 45 | public String getStationId() { 46 | return stationId; 47 | } 48 | 49 | public int getX() { 50 | return x; 51 | } 52 | 53 | public int getY() { 54 | return y; 55 | } 56 | 57 | public void setKey(Long key) { 58 | this.key = key; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public void setNetworkPlanKey(Long networkPlanKey) { 66 | this.networkPlanKey = networkPlanKey; 67 | } 68 | 69 | public void setStationId(String stationId) { 70 | this.stationId = stationId; 71 | } 72 | 73 | public void setX(int x) { 74 | this.x = x; 75 | } 76 | 77 | public void setY(int y) { 78 | this.y = y; 79 | } 80 | 81 | @Override 82 | public String toJSONString() { 83 | return new JSONObject(this).toString(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/NetworkPlansResource.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.util.List; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONObject; 7 | import org.restlet.data.MediaType; 8 | import org.restlet.data.Method; 9 | import org.restlet.data.Status; 10 | import org.restlet.ext.json.JsonRepresentation; 11 | import org.restlet.representation.Representation; 12 | import org.restlet.representation.Variant; 13 | import org.restlet.resource.ResourceException; 14 | 15 | import com.pangratz.oeffinpc.model.NetworkPlan; 16 | 17 | public class NetworkPlansResource extends OeffiNpcServerResource { 18 | 19 | @Override 20 | protected void doInit() throws ResourceException { 21 | super.doInit(); 22 | 23 | getVariants(Method.GET).add(new Variant(MediaType.APPLICATION_JSON)); 24 | getVariants(Method.POST).add(new Variant(MediaType.APPLICATION_JSON)); 25 | } 26 | 27 | @Override 28 | protected Representation get(Variant variant) throws ResourceException { 29 | List networkPlans = mModelUtils.getNetworkPlans(); 30 | if (networkPlans.isEmpty()) { 31 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 32 | return null; 33 | } 34 | 35 | JSONArray arr = new JSONArray(networkPlans); 36 | return new JsonRepresentation(arr); 37 | } 38 | 39 | @Override 40 | protected Representation post(Representation entity, Variant variant) throws ResourceException { 41 | JsonRepresentation represent; 42 | try { 43 | represent = new JsonRepresentation(entity); 44 | JSONObject json = represent.getJsonObject(); 45 | 46 | NetworkPlan networkPlan = new NetworkPlan(); 47 | networkPlan.setImageUrl(json.getString("imageUrl")); 48 | networkPlan.setNetworkId(json.getString("networkId")); 49 | networkPlan.setPlanId(json.getString("planId")); 50 | 51 | mModelUtils.storeNetworkPlan(networkPlan); 52 | 53 | return createResourceCreatedRepresentation(networkPlan); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | setStatus(Status.SERVER_ERROR_INTERNAL, e); 57 | } 58 | 59 | return createErrorRepresentation("error while storing NetworkPlan"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/OeffiNpcServerResource.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.restlet.data.MediaType; 7 | import org.restlet.data.Reference; 8 | import org.restlet.data.Status; 9 | import org.restlet.ext.json.JsonRepresentation; 10 | import org.restlet.representation.Representation; 11 | import org.restlet.representation.StringRepresentation; 12 | import org.restlet.representation.Variant; 13 | import org.restlet.resource.ResourceException; 14 | import org.restlet.resource.ServerResource; 15 | 16 | import com.pangratz.oeffinpc.model.ModelUtils; 17 | import com.pangratz.oeffinpc.model.NetworkPlan; 18 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 19 | 20 | public abstract class OeffiNpcServerResource extends ServerResource { 21 | 22 | protected ModelUtils mModelUtils; 23 | 24 | public OeffiNpcServerResource() { 25 | super(); 26 | 27 | getVariants().add(new Variant(MediaType.APPLICATION_JSON)); 28 | } 29 | 30 | protected Representation createErrorRepresentation(String errorMsg) { 31 | Map data = new HashMap(); 32 | data.put("errorMsg", errorMsg); 33 | return new JsonRepresentation(data); 34 | } 35 | 36 | protected Representation createResourceCreatedRepresentation(NetworkPlan networkPlan) { 37 | String id = "/networkplans/" + networkPlan.getKey(); 38 | return createResourceCreatedRepresentation(id, networkPlan); 39 | } 40 | 41 | protected Representation createResourceCreatedRepresentation(NetworkPlanEntry networkPlanEntry) { 42 | String id = "/networkplanentries/" + networkPlanEntry.getKey(); 43 | return createResourceCreatedRepresentation(id, networkPlanEntry); 44 | } 45 | 46 | protected Representation createResourceCreatedRepresentation(String id, Object data) { 47 | setStatus(Status.SUCCESS_CREATED); 48 | Representation result = new JsonRepresentation(data); 49 | Reference hostRef = getRequest().getHostRef(); 50 | String host = hostRef.toString(); 51 | result.setLocationRef(host + id); 52 | return result; 53 | } 54 | 55 | protected Representation createResourceUpdatedRepresentation(String stationId) { 56 | setStatus(Status.SUCCESS_NO_CONTENT); 57 | Representation result = new StringRepresentation("updated"); 58 | return result; 59 | } 60 | 61 | @Override 62 | protected void doInit() throws ResourceException { 63 | super.doInit(); 64 | 65 | this.mModelUtils = ModelUtils.getInstance(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /run_server.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var http = require('http'); 3 | var express = require('express'); 4 | var app = express.createServer(); 5 | app.use(express.bodyParser()); 6 | 7 | var CouchClient = require('couch-client'); 8 | var oeffinpc = new CouchClient('http://localhost:5984/oeffinpc'); 9 | 10 | app.get('/networkplans', function(req, res){ 11 | oeffinpc.view('/oeffinpc/_design/oeffinpc/_view/all_networkplans', {}, function(err, doc){ 12 | if (doc.rows) { 13 | var networkPlans = doc.rows.map(function(val){ 14 | return val.value; 15 | }); 16 | res.send(networkPlans); 17 | } 18 | }); 19 | }); 20 | 21 | app.put('/networkplans/:key', function(req, res){ 22 | res.send('ok'); 23 | }); 24 | 25 | app.post('/networkplans/:key', function(req, res){ 26 | var body = req.body; 27 | var key = req.params.key; 28 | body['_id'] = undefined; 29 | body['networkPlanKey'] = key; 30 | body['x'] = body.x ? body.x : 0; 31 | body['y'] = body.y ? body.y : 0; 32 | oeffinpc.save(body, function(err, doc){ 33 | doc['key'] = doc._id; 34 | res.send(doc); 35 | }); 36 | }); 37 | 38 | app.get('/networkplans/:key', function(req, res){ 39 | var key = req.params.key; 40 | oeffinpc.get(key, function(err, doc){ 41 | res.send(doc); 42 | }); 43 | }); 44 | 45 | app.get('/networkplans/:key/_entries', function(req, res){ 46 | var key = req.params.key; 47 | oeffinpc.view('/oeffinpc/_design/oeffinpc/_view/all_networkplanentries?key="'+key+'"', {}, function(err, doc){ 48 | if (doc && doc.rows) { 49 | var entries = doc.rows.map(function(val){ 50 | var entry = val.value; 51 | entry['key'] = entry._id; 52 | return entry; 53 | }); 54 | res.send(entries); 55 | } 56 | }); 57 | }); 58 | 59 | app.get('/networkplanentries/:key', function(req, res){ 60 | var key = req.params.key; 61 | oeffinpc.view('/oeffinpc/_design/oeffinpc/_view/networkplanentry?key="'+key+'"', {}, function(err, doc){ 62 | var rows = doc.rows; 63 | if (rows) { 64 | res.send(rows[0].value); 65 | } 66 | res.send(null); 67 | }); 68 | }); 69 | 70 | app.put('/networkplanentries/:key', function(req, res){ 71 | var body = req.body; 72 | var key = req.params.key; 73 | oeffinpc.get(key, function(err, doc) { 74 | body['_rev'] = doc._rev; 75 | body['_id'] = key; 76 | oeffinpc.save(body, function(err, doc2){ 77 | res.send(doc2); 78 | }); 79 | }); 80 | }); 81 | 82 | app.del('/networkplanentries/:key', function(req, res){ 83 | var key = req.params.key; 84 | oeffinpc.remove(key, function(err, doc){ 85 | res.send(doc); 86 | }); 87 | }); 88 | 89 | app.listen(3000); -------------------------------------------------------------------------------- /run_dev_server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var child_process = require('child_process'); 10 | var fs = require("fs"); 11 | var sys = require("sys"); 12 | 13 | dev_server = { 14 | 15 | process: null, 16 | 17 | files: [], 18 | 19 | restarting: false, 20 | 21 | "restart": function() { 22 | this.restarting = true; 23 | sys.debug('DEVSERVER: Stopping server for restart'); 24 | this.process.kill(); 25 | }, 26 | 27 | "start": function() { 28 | var self = this; 29 | sys.debug('DEVSERVER: Starting server'); 30 | self.watchFiles(); 31 | 32 | this.process = child_process.spawn(process.ARGV[0], ['run_server.js']); 33 | 34 | this.process.stdout.addListener('data', function (data) { 35 | process.stdout.write(data); 36 | }); 37 | 38 | this.process.stderr.addListener('data', function (data) { 39 | sys.print(data); 40 | }); 41 | 42 | this.process.addListener('exit', function (code) { 43 | sys.debug('DEVSERVER: Child process exited: ' + code); 44 | this.process = null; 45 | if (self.restarting) { 46 | self.restarting = true; 47 | self.unwatchFiles(); 48 | self.start(); 49 | } 50 | }); 51 | 52 | }, 53 | 54 | "watchFiles": function() { 55 | var self = this; 56 | 57 | child_process.exec('find . | grep "\.js$"', function(error, stdout, stderr) { 58 | var files = stdout.trim().split("\n"); 59 | 60 | files.forEach(function(file) { 61 | self.files.push(file); 62 | fs.watchFile(file, {interval : 500}, function(curr, prev) { 63 | if (curr.mtime.valueOf() != prev.mtime.valueOf() || curr.ctime.valueOf() != prev.ctime.valueOf()) { 64 | sys.debug('DEVSERVER: Restarting because of changed file at ' + file); 65 | dev_server.restart(); 66 | } 67 | }); 68 | }); 69 | }); 70 | }, 71 | 72 | "unwatchFiles": function() { 73 | this.files.forEach(function(file) { 74 | fs.unwatchFile(file); 75 | }); 76 | this.files = []; 77 | } 78 | } 79 | 80 | 81 | dev_server.start(); 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/NetworkPlanEntryResource.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.json.JSONObject; 7 | import org.restlet.data.Status; 8 | import org.restlet.ext.json.JsonRepresentation; 9 | import org.restlet.representation.Representation; 10 | import org.restlet.representation.Variant; 11 | import org.restlet.resource.ResourceException; 12 | 13 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 14 | 15 | public class NetworkPlanEntryResource extends OeffiNpcServerResource { 16 | 17 | private Long mStationKey; 18 | 19 | @Override 20 | protected Representation delete(Variant variant) throws ResourceException { 21 | mModelUtils.removeNetworkPlanEntry(mStationKey); 22 | Map data = new HashMap(); 23 | data.put("deleted", true); 24 | return new JsonRepresentation(data); 25 | } 26 | 27 | @Override 28 | protected void doInit() throws ResourceException { 29 | super.doInit(); 30 | 31 | String stringVal = (String) getRequest().getAttributes().get("stationId"); 32 | this.mStationKey = Long.valueOf(stringVal); 33 | } 34 | 35 | @Override 36 | protected Representation get(Variant variant) throws ResourceException { 37 | NetworkPlanEntry networkPlanEntry = mModelUtils.getNetworkPlanEntry(mStationKey); 38 | if (networkPlanEntry == null) { 39 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 40 | return createErrorRepresentation("NetworkPlanEntry with given id not found"); 41 | } 42 | 43 | return new JsonRepresentation(networkPlanEntry); 44 | } 45 | 46 | @Override 47 | protected Representation put(Representation entity, Variant variant) throws ResourceException { 48 | NetworkPlanEntry oldEntry = mModelUtils.getNetworkPlanEntry(mStationKey); 49 | if (oldEntry == null) { 50 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 51 | return createErrorRepresentation("NetworkPlanEntry with given id not found"); 52 | } 53 | 54 | JsonRepresentation represent; 55 | try { 56 | represent = new JsonRepresentation(entity); 57 | JSONObject json = represent.getJsonObject(); 58 | 59 | NetworkPlanEntry networkPlanEntry = new NetworkPlanEntry(); 60 | networkPlanEntry.setKey(oldEntry.getKey()); 61 | networkPlanEntry.setNetworkPlanKey(oldEntry.getNetworkPlanKey()); 62 | networkPlanEntry.setStationId(json.getString("stationId")); 63 | 64 | if (json.has("name")) { 65 | networkPlanEntry.setName(json.getString("name")); 66 | } 67 | if (json.has("x")) { 68 | networkPlanEntry.setX(json.getInt("x")); 69 | } 70 | if (json.has("y")) { 71 | networkPlanEntry.setY(json.getInt("y")); 72 | } 73 | 74 | mModelUtils.storeNetworkPlanEntry(networkPlanEntry); 75 | 76 | return super.createResourceUpdatedRepresentation(networkPlanEntry.getStationId()); 77 | } catch (Exception e) { 78 | e.printStackTrace(); 79 | setStatus(Status.SERVER_ERROR_INTERNAL); 80 | } 81 | 82 | return createErrorRepresentation("error while updating NetworkPlanEntry"); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/NetworkPlanEntriesResource.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.json.JSONArray; 8 | import org.restlet.data.MediaType; 9 | import org.restlet.data.Method; 10 | import org.restlet.data.Status; 11 | import org.restlet.ext.freemarker.TemplateRepresentation; 12 | import org.restlet.ext.json.JsonRepresentation; 13 | import org.restlet.representation.Representation; 14 | import org.restlet.representation.Variant; 15 | import org.restlet.resource.ResourceException; 16 | 17 | import com.pangratz.oeffinpc.model.NetworkPlan; 18 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 19 | 20 | import freemarker.cache.ClassTemplateLoader; 21 | import freemarker.template.Configuration; 22 | 23 | public class NetworkPlanEntriesResource extends OeffiNpcServerResource { 24 | 25 | private Long mNetworkPlanId; 26 | 27 | @Override 28 | protected Representation delete(Variant variant) throws ResourceException { 29 | NetworkPlan networkPlan = mModelUtils.getNetworkPlan(mNetworkPlanId); 30 | if (networkPlan == null) { 31 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 32 | return createErrorRepresentation("no network plan with id " + mNetworkPlanId); 33 | } 34 | 35 | long removedEntriesCount = mModelUtils.removeNetworkPlanEntries(mNetworkPlanId); 36 | Map data = new HashMap(); 37 | data.put("removedEntriesCount", removedEntriesCount); 38 | return new JsonRepresentation(data); 39 | } 40 | 41 | @Override 42 | protected void doInit() throws ResourceException { 43 | super.doInit(); 44 | 45 | String stringVal = (String) getRequest().getAttributes().get("networkPlanId"); 46 | System.out.println("NetworkPlanEntriesResource#stringVal = " + stringVal); 47 | this.mNetworkPlanId = Long.valueOf(stringVal); 48 | 49 | getVariants(Method.DELETE).add(new Variant(MediaType.APPLICATION_JSON)); 50 | getVariants(Method.GET).add(new Variant(MediaType.TEXT_CSV)); 51 | getVariants(Method.GET).add(new Variant(MediaType.APPLICATION_JSON)); 52 | } 53 | 54 | @Override 55 | protected Representation get(Variant variant) throws ResourceException { 56 | NetworkPlan networkPlan = mModelUtils.getNetworkPlan(mNetworkPlanId); 57 | if (networkPlan == null) { 58 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 59 | return createErrorRepresentation("no network plan with id " + mNetworkPlanId); 60 | } 61 | 62 | List entries = mModelUtils.getNetworkPlanEntries(mNetworkPlanId); 63 | 64 | if (MediaType.TEXT_CSV.equals(variant.getMediaType())) { 65 | String templateName = "template.tfl"; 66 | Configuration config = new Configuration(); 67 | ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "/com/pangratz/oeffinpc"); 68 | config.setTemplateLoader(ctl); 69 | Map model = new HashMap(); 70 | model.put("networkPlan", networkPlan); 71 | model.put("entries", entries); 72 | return new TemplateRepresentation(templateName, config, model, MediaType.TEXT_CSV); 73 | } 74 | 75 | JSONArray entriesArr = new JSONArray(entries); 76 | return new JsonRepresentation(entriesArr); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /gae-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This script installs the Google App Engine artifacts into you local Maven repository. 5 | # 6 | # If you would like to avoid the need for this step, ask Google by voting for the following issue: 7 | # http://code.google.com/p/googleappengine/issues/detail?id=1296 8 | # 9 | 10 | SDK_VERSION=1.5.1 11 | 12 | if [ "$1" == "" ] 13 | then 14 | echo "usage: $0 /path/to/appengine-java-sdk-$SDK_VERSION [install|deploy]" 15 | exit 1 16 | fi 17 | 18 | if [ "$2" == "" ] 19 | then 20 | echo "usage: $0 /path/to/appengine-java-sdk-$SDK_VERSION [install|deploy]" 21 | exit 1 22 | fi 23 | 24 | GAE_SDK_PATH="$1" 25 | TASK="$2" 26 | URL="svn:https://maven-gae-plugin.googlecode.com/svn/repository" 27 | 28 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/appengine-api-1.0-sdk-$SDK_VERSION.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-1.0-sdk -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar 29 | 30 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/docs/javadoc/appengine-api-1.0-sdk-$SDK_VERSION-javadoc.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-1.0-sdk -Dversion=$SDK_VERSION -DgeneratePom=true -Dclassifier=javadoc -Dpackaging=jar 31 | 32 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/appengine-api-labs-$SDK_VERSION.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-labs -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar 33 | 34 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/appengine-tools-api.jar -DgroupId=com.google.appengine -DartifactId=appengine-tools-sdk -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar 35 | 36 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/impl/appengine-api-stubs.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-stubs -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar 37 | 38 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/testing/appengine-testing.jar -DgroupId=com.google.appengine -DartifactId=appengine-testing -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar 39 | 40 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/docs/testing/javadoc/appengine-testing-javadoc.jar -DgroupId=com.google.appengine -DartifactId=appengine-testing -Dversion=$SDK_VERSION -DgeneratePom=true -Dclassifier=javadoc -Dpackaging=jar 41 | 42 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/orm/jdo2-api-2.3-eb.jar -DgroupId=com.google.appengine.orm -DartifactId=jdo2-api -Dversion=2.3-eb -DgeneratePom=true -Dpackaging=jar 43 | 44 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/orm/datanucleus-appengine-1.0.7.final.jar -DgroupId=com.google.appengine.orm -DartifactId=datanucleus-appengine -Dversion=1.0.7 -DgeneratePom=true -Dpackaging=jar 45 | 46 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/orm/datanucleus-core-1.1.5.jar -DgroupId=com.google.appengine.orm -DartifactId=datanucleus-core -Dversion=1.1.5 -DgeneratePom=true -Dpackaging=jar 47 | 48 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/orm/datanucleus-jpa-1.1.5.jar -DgroupId=com.google.appengine.orm -DartifactId=datanucleus-jpa -Dversion=1.1.5 -DgeneratePom=true -Dpackaging=jar 49 | 50 | mvn $TASK:$TASK-file -Durl=$URL -Dfile=$GAE_SDK_PATH/lib/user/appengine-jsr107cache-$SDK_VERSION.jar -DgroupId=com.google.appengine -DartifactId=jsr107cache -Dversion=$SDK_VERSION -DgeneratePom=true -Dpackaging=jar -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/model/ModelUtils.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import javax.jdo.PersistenceManager; 8 | import javax.jdo.PersistenceManagerFactory; 9 | import javax.jdo.Query; 10 | 11 | public class ModelUtils { 12 | 13 | private static final ModelUtils INSTANCE = new ModelUtils(); 14 | 15 | public static ModelUtils getInstance() { 16 | return INSTANCE; 17 | } 18 | 19 | private PersistenceManagerFactory mPMF; 20 | 21 | private ModelUtils() { 22 | super(); 23 | mPMF = PMF.get(); 24 | } 25 | 26 | public NetworkPlan getNetworkPlan(Long networkId) { 27 | if (networkId == null) { 28 | return null; 29 | } 30 | 31 | PersistenceManager pm = mPMF.getPersistenceManager(); 32 | try { 33 | Query query = pm.newQuery(NetworkPlan.class, "key == networkIdParam"); 34 | query.declareParameters("Long networkIdParam"); 35 | List networkPlans = (List) query.execute(networkId); 36 | if (networkPlans != null && networkPlans.size() == 1) { 37 | return networkPlans.get(0); 38 | } 39 | return null; 40 | } finally { 41 | pm.close(); 42 | } 43 | } 44 | 45 | public List getNetworkPlanEntries(Long networkId) { 46 | PersistenceManager pm = mPMF.getPersistenceManager(); 47 | try { 48 | Query query = pm.newQuery(NetworkPlanEntry.class, "networkPlanKey == networkIdParam"); 49 | query.setOrdering("name asc"); 50 | query.declareParameters("Long networkIdParam"); 51 | List networkPlanEntries = (List) query.execute(networkId); 52 | if (networkPlanEntries != null) 53 | return (List) pm.detachCopyAll(networkPlanEntries); 54 | 55 | return new ArrayList(); 56 | } finally { 57 | pm.close(); 58 | } 59 | } 60 | 61 | public NetworkPlanEntry getNetworkPlanEntry(Long stationId) { 62 | if (stationId == null) { 63 | return null; 64 | } 65 | 66 | PersistenceManager pm = mPMF.getPersistenceManager(); 67 | try { 68 | Query query = pm.newQuery(NetworkPlanEntry.class, "key == stationId"); 69 | query.declareParameters("Long stationId"); 70 | List networkPlanEntries = (List) query.execute(stationId); 71 | if (networkPlanEntries != null && networkPlanEntries.size() == 1) { 72 | return networkPlanEntries.get(0); 73 | } 74 | return null; 75 | } finally { 76 | pm.close(); 77 | } 78 | } 79 | 80 | public List getNetworkPlans() { 81 | PersistenceManager pm = mPMF.getPersistenceManager(); 82 | try { 83 | List networkPlans = (List) pm.newQuery(NetworkPlan.class).execute(); 84 | if (networkPlans != null) 85 | return (List) pm.detachCopyAll(networkPlans); 86 | 87 | return new ArrayList(); 88 | } finally { 89 | pm.close(); 90 | } 91 | } 92 | 93 | public long removeNetworkPlanEntries(Long networkPlanKey) { 94 | PersistenceManager pm = mPMF.getPersistenceManager(); 95 | try { 96 | Query query = pm.newQuery(NetworkPlanEntry.class, "networkPlanKey == keyParam"); 97 | query.declareParameters("Long keyParam"); 98 | return query.deletePersistentAll(networkPlanKey); 99 | } finally { 100 | pm.close(); 101 | } 102 | } 103 | 104 | public void removeNetworkPlanEntry(Long stationKey) { 105 | PersistenceManager pm = mPMF.getPersistenceManager(); 106 | try { 107 | Query query = pm.newQuery(NetworkPlanEntry.class, "key == keyParam"); 108 | query.declareParameters("Long keyParam"); 109 | query.deletePersistentAll(stationKey); 110 | } finally { 111 | pm.close(); 112 | } 113 | } 114 | 115 | public Long storeNetworkPlan(NetworkPlan networkPlan) { 116 | PersistenceManager pm = mPMF.getPersistenceManager(); 117 | try { 118 | NetworkPlan persistent = pm.makePersistent(networkPlan); 119 | return persistent.getKey(); 120 | } finally { 121 | pm.close(); 122 | } 123 | } 124 | 125 | public int storeNetworkPlanEntries(Long networkPlanKey, List entries) { 126 | if (networkPlanKey == null || entries == null) { 127 | return 0; 128 | } 129 | 130 | PersistenceManager pm = mPMF.getPersistenceManager(); 131 | try { 132 | for (NetworkPlanEntry entry : entries) { 133 | entry.setNetworkPlanKey(networkPlanKey); 134 | } 135 | Collection persisted = pm.makePersistentAll(entries); 136 | return persisted.size(); 137 | } finally { 138 | pm.close(); 139 | } 140 | } 141 | 142 | public Long storeNetworkPlanEntry(NetworkPlanEntry networkPlanEntry) { 143 | PersistenceManager pm = mPMF.getPersistenceManager(); 144 | try { 145 | NetworkPlanEntry persistent = pm.makePersistent(networkPlanEntry); 146 | return persistent.getKey(); 147 | } finally { 148 | pm.close(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/pangratz/oeffinpc/rest/NetworkPlanResource.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.rest; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.commons.fileupload.FileItemIterator; 9 | import org.apache.commons.fileupload.FileItemStream; 10 | import org.apache.commons.fileupload.FileUploadException; 11 | import org.apache.commons.fileupload.servlet.ServletFileUpload; 12 | import org.json.JSONObject; 13 | import org.restlet.data.MediaType; 14 | import org.restlet.data.Method; 15 | import org.restlet.data.Status; 16 | import org.restlet.ext.json.JsonRepresentation; 17 | import org.restlet.ext.servlet.ServletUtils; 18 | import org.restlet.representation.Representation; 19 | import org.restlet.representation.Variant; 20 | import org.restlet.resource.ResourceException; 21 | 22 | import com.pangratz.oeffinpc.model.NetworkPlan; 23 | import com.pangratz.oeffinpc.model.NetworkPlanEntry; 24 | import com.pangratz.oeffinpc.util.CsvUtils; 25 | 26 | public class NetworkPlanResource extends OeffiNpcServerResource { 27 | 28 | private Long mNetworkPlanId; 29 | 30 | private Representation handleCsvFilePost(Representation entity) { 31 | // implemented as described here: 32 | // http://wiki.restlet.org/docs_2.1/13-restlet/28-restlet/64-restlet.html 33 | // and here: 34 | // http://code.google.com/appengine/kb/java.html#fileforms 35 | 36 | try { 37 | ServletFileUpload upload = new ServletFileUpload(); 38 | FileItemIterator iterator = upload.getItemIterator(ServletUtils.getRequest(getRequest())); 39 | while (iterator.hasNext()) { 40 | FileItemStream fileItemStream = iterator.next(); 41 | List entries = CsvUtils.getInstance().readCsv(fileItemStream.openStream()); 42 | int numberOfEntries = this.mModelUtils.storeNetworkPlanEntries(mNetworkPlanId, entries); 43 | Map dataMap = new HashMap(); 44 | dataMap.put("numberOfSavedEntries", numberOfEntries); 45 | return new JsonRepresentation(dataMap); 46 | } 47 | 48 | return createErrorRepresentation("no files uploaded"); 49 | } catch (FileUploadException e) { 50 | e.printStackTrace(); 51 | return createErrorRepresentation("error while uploading file: " + e.getMessage()); 52 | } catch (IOException e) { 53 | e.printStackTrace(); 54 | return createErrorRepresentation("error while parsing uploaded CSV file: " + e.getMessage()); 55 | } 56 | 57 | } 58 | 59 | private Representation handleJsonPost(Representation entity) { 60 | JsonRepresentation represent; 61 | try { 62 | represent = new JsonRepresentation(entity); 63 | JSONObject json = represent.getJsonObject(); 64 | 65 | NetworkPlanEntry networkPlanEntry = new NetworkPlanEntry(); 66 | networkPlanEntry.setNetworkPlanKey(mNetworkPlanId); 67 | networkPlanEntry.setStationId(json.getString("stationId")); 68 | 69 | if (json.has("name")) { 70 | networkPlanEntry.setName(json.getString("name")); 71 | } 72 | if (json.has("x")) { 73 | networkPlanEntry.setX(json.getInt("x")); 74 | } 75 | if (json.has("y")) { 76 | networkPlanEntry.setY(json.getInt("y")); 77 | } 78 | 79 | mModelUtils.storeNetworkPlanEntry(networkPlanEntry); 80 | 81 | return createResourceCreatedRepresentation(networkPlanEntry); 82 | } catch (Exception e) { 83 | e.printStackTrace(); 84 | setStatus(Status.SERVER_ERROR_INTERNAL); 85 | } 86 | 87 | return createErrorRepresentation("error while creating NetworkPlanEntry"); 88 | } 89 | 90 | @Override 91 | protected void doInit() throws ResourceException { 92 | super.doInit(); 93 | 94 | String stringVal = (String) getRequest().getAttributes().get("networkPlanId"); 95 | System.out.println("NetworkPlanResource#stringVal = " + stringVal); 96 | this.mNetworkPlanId = Long.valueOf(stringVal); 97 | 98 | getVariants(Method.GET).add(new Variant(MediaType.APPLICATION_JSON)); 99 | getVariants(Method.POST).add(new Variant(MediaType.APPLICATION_JSON)); 100 | getVariants(Method.POST).add(new Variant(MediaType.MULTIPART_FORM_DATA)); 101 | } 102 | 103 | @Override 104 | protected Representation get(Variant variant) throws ResourceException { 105 | NetworkPlan networkPlan = mModelUtils.getNetworkPlan(mNetworkPlanId); 106 | if (networkPlan == null) { 107 | setStatus(Status.CLIENT_ERROR_NOT_FOUND); 108 | return createErrorRepresentation("no network plan with id " + mNetworkPlanId); 109 | } 110 | 111 | JSONObject networkPlanObj = new JSONObject(networkPlan); 112 | return new JsonRepresentation(networkPlanObj); 113 | } 114 | 115 | @Override 116 | protected Representation post(Representation entity, Variant variant) throws ResourceException { 117 | Representation result = null; 118 | System.out.println(variant); 119 | if (entity != null) { 120 | if (MediaType.MULTIPART_FORM_DATA.equals(entity.getMediaType(), true)) { 121 | result = handleCsvFilePost(entity); 122 | } else if (MediaType.APPLICATION_JSON.equals(entity.getMediaType(), true)) { 123 | result = handleJsonPost(entity); 124 | } 125 | } 126 | return result; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/data_sources/rest_data_source.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.RestDataSource 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your Data Source Here) 10 | 11 | @extends SC.DataSource 12 | */ 13 | OeffiNpc.RestDataSource = SC.DataSource.extend( 14 | /** @scope OeffiNpc.RestDataSource.prototype */ { 15 | 16 | fetch: function(store, query) { 17 | var options = { 18 | store: store, 19 | query: query, 20 | isQuery: YES 21 | }; 22 | 23 | var recordType = query.get('recordType'); 24 | if (OeffiNpc.NetworkPlan === recordType) { 25 | options['type'] = OeffiNpc.NetworkPlan; 26 | return this._getFromUri('/networkplans', options); 27 | } else if (OeffiNpc.NetworkPlanEntry === recordType) { 28 | options['type'] = OeffiNpc.NetworkPlanEntry; 29 | var networkPlanKey = query.parameters.networkPlanKey; 30 | var url = '/networkplans/' + networkPlanKey + '/_entries'; 31 | return this._getFromUri(url, options); 32 | } 33 | 34 | return NO; 35 | }, 36 | 37 | retrieveRecord: function(store, storeKey, id) { 38 | SC.debug('retrieveRecord'); 39 | var type = store.recordTypeFor(storeKey); 40 | var hash = store.readDataHash(storeKey); 41 | var url = this._urlFor(type, id, hash); 42 | SC.Request.getUrl(url) 43 | .json() 44 | .notify(this, this._didRetrieveRecord, store, storeKey) 45 | .send(); 46 | 47 | return YES; 48 | }, 49 | 50 | _didRetrieveRecord: function(response, store, storeKey, type){ 51 | SC.debug('#_didRetrieveRecord'); 52 | if (SC.ok(response)) { 53 | var body = response.get('body'); 54 | SC.debug('_didRetrieveRecord'); 55 | store.loadRecords(type, body); 56 | store.dataSourceDidComplete(storeKey, body, body.key); 57 | } else { 58 | store.dataSourceDidError(storeKey); 59 | } 60 | }, 61 | 62 | createRecord: function(store, storeKey) { 63 | SC.debug("#createRecord"); 64 | var type = store.recordTypeFor(storeKey); 65 | if (OeffiNpc.NetworkPlan === type) { 66 | SC.debug('tried to create NetworkPlan'); 67 | return NO; 68 | } 69 | var hash = store.readDataHash(storeKey); 70 | var id = store.idFor(storeKey); 71 | var url = this._urlFor(type, id, hash); 72 | var body = hash; 73 | SC.Request.postUrl(url) 74 | .json() 75 | .notify(this, this._didCreateRecord, store, storeKey, type) 76 | .send(body); 77 | 78 | return YES; 79 | }, 80 | 81 | _didCreateRecord: function(response, store, key) { 82 | SC.debug("#_didCreateRecord"); 83 | if (SC.ok(response)) { 84 | SC.debug('invoking store#dataSourceDidComplete for ' + key); 85 | var body = response.get('body'); 86 | var id = body.key; 87 | store.dataSourceDidComplete(key, body, id); 88 | } else { 89 | SC.debug('invoking store#dataSourceDidError for ' + key); 90 | store.dataSourceDidError(key); 91 | } 92 | }, 93 | 94 | destroyRecord: function(store, storeKey) { 95 | SC.debug('#destroyRecord'); 96 | var type = store.recordTypeFor(storeKey); 97 | var id = store.idFor(storeKey); 98 | var hash = store.readDataHash(storeKey); 99 | 100 | var url = this._urlFor(type, id, hash); 101 | SC.debug('delete ' + url); 102 | SC.Request.deleteUrl(url) 103 | .json() 104 | .notify(this, this._didDestroyRecord, store, storeKey) 105 | .send(); 106 | 107 | return YES; 108 | }, 109 | 110 | _didDestroyRecord: function(response, store, storeKey) { 111 | SC.debug('#_didDestroyRecord'); 112 | if (SC.ok(response)) { 113 | store.dataSourceDidDestroy(storeKey); 114 | } else { 115 | store.dataSourceDidError(storeKey); 116 | } 117 | }, 118 | 119 | updateRecord: function(store, storeKey) { 120 | SC.debug("#updateRecord"); 121 | var type = store.recordTypeFor(storeKey); 122 | var hash = store.readDataHash(storeKey); 123 | var id = store.idFor(storeKey); 124 | var body = hash; 125 | var url = this._urlFor(type, id, hash); 126 | 127 | SC.Request.putUrl(url) 128 | .header({'Content-Type': 'application/json; charset=utf-8'}) 129 | .json() 130 | .notify(this, this._didUpdateRecord, store, storeKey) 131 | .send(body); 132 | 133 | return YES; 134 | }, 135 | 136 | _didUpdateRecord: function(response, store, key) { 137 | SC.debug("#_didUpdateRecord"); 138 | if (SC.ok(response)) { 139 | SC.debug('invoking store#dataSourceDidComplete for ' + key); 140 | store.dataSourceDidComplete(key); 141 | } else { 142 | SC.debug('invoking store#dataSourceDidError for ' + key); 143 | store.dataSourceDidError(key); 144 | } 145 | }, 146 | 147 | _urlFor: function(type, id, hash) { 148 | if (OeffiNpc.NetworkPlanEntry === type) { 149 | if (id) { 150 | return '/networkplanentries/%@1'.fmt(id); 151 | } else { 152 | return '/networkplans/%@1'.fmt(hash.networkPlanKey); 153 | } 154 | } 155 | 156 | if (OeffiNpc.NetworkPlan === type) { 157 | return '/networkplans/%@1'.fmt(id); 158 | } 159 | 160 | return undefined; 161 | }, 162 | 163 | _getFromUri: function(uri, options) { 164 | SC.Request 165 | .getUrl(uri) 166 | .json() 167 | .notify(this, '_didGetQuery', options) 168 | .send(); 169 | 170 | return YES; 171 | }, 172 | 173 | _didGetQuery: function(response, params) { 174 | SC.debug('#_didGetQuery'); 175 | var store = params.store; 176 | var query = params.query; 177 | var type = params.type; 178 | var deffered = params.deffered; 179 | 180 | if (SC.ok(response)) { 181 | var body = response.get('body'); 182 | if (SC.isArray(body)) { 183 | store.loadRecords(type, body); 184 | } else { 185 | store.loadRecord(type, body); 186 | } 187 | store.dataSourceDidFetchQuery(query); 188 | } else { 189 | store.dataSourceDidErrorQuery(query, response); 190 | } 191 | } 192 | 193 | }); 194 | -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/resources/main_page.js: -------------------------------------------------------------------------------- 1 | /*globals OeffiNpc*/ 2 | 3 | OeffiNpc.mainPage = SC.Page.design({ 4 | 5 | mainPane: SC.MainPane.design({ 6 | 7 | childViews: 'topView entriesView entryView'.w(), 8 | layout: {left: 0, right: 0, top: 0, bottom: 0}, 9 | 10 | keyUp: function(evt) { 11 | if (evt.keyCode == 90) { 12 | OeffiNpc.statechart.sendEvent('zPressed'); 13 | return YES; 14 | } 15 | 16 | if (evt.keyCode >= 49 && evt.keyCode <= 57) { 17 | OeffiNpc.statechart.sendEvent('numberPressed', (evt.keyCode - 48)); 18 | return YES; 19 | } 20 | 21 | return NO; 22 | }, 23 | 24 | topView: SC.View.design({ 25 | layout: {top: 5, left: 0, right: 0, height: 31}, 26 | childViews: 'backButton labelView'.w(), 27 | 28 | backButton: SC.ButtonView.design({ 29 | layout: {left: 10, width: 50}, 30 | title: 'back', 31 | target: 'OeffiNpc.statechart', 32 | action: 'backToNetworkPlans' 33 | }), 34 | 35 | labelView: SC.LabelView.design({ 36 | layout: {left: 70, right: 10}, 37 | valueBinding: 'OeffiNpc.networkPlanController.networkId' 38 | }) 39 | 40 | }), 41 | 42 | entriesView: SC.View.design({ 43 | layout: {left: 10, width: 300, top: 36, bottom: 10}, 44 | childViews: 'list buttons editItemView'.w(), 45 | 46 | list: SC.ScrollView.design({ 47 | layout: {top: 0, bottom: 256, left: 0, right: 0}, 48 | contentView: SC.ListView.design({ 49 | showAlternatingRows: YES, 50 | contentBinding: 'OeffiNpc.networkPlanEntriesController.arrangedObjects', 51 | selectionBinding: 'OeffiNpc.networkPlanEntryController.content', 52 | contentValueKey: 'name' 53 | }) 54 | }), 55 | 56 | buttons: SC.View.design({ 57 | childViews: 'removeBtn addBtn'.w(), 58 | layout: {bottom: 210, height: 36, left: 0, right: 0}, 59 | 60 | removeBtn: SC.ButtonView.design({ 61 | layout: {bottom: 0, right: 36, width: 24}, 62 | title: '-', 63 | target: 'OeffiNpc.statechart', 64 | action: 'removeEntry' 65 | }), 66 | 67 | addBtn: SC.ButtonView.design({ 68 | layout: {bottom: 0, right: 10, width: 24}, 69 | title: '+', 70 | target: 'OeffiNpc.statechart', 71 | action: 'addEntry' 72 | }) 73 | }), 74 | 75 | editItemView: SC.View.design({ 76 | layout: {left: 0, right: 0, bottom: 0, height: 200}, 77 | childViews: 'name station position info'.w(), 78 | 79 | name: SC.View.design({ 80 | layout: {top: 10, left: 10, right: 10, height: 26}, 81 | childViews: 'label name'.w(), 82 | label: SC.LabelView.design({ 83 | layout: {left: 5, width: 70}, 84 | value: 'Name' 85 | }), 86 | name: SC.TextFieldView.design({ 87 | layout: {left: 80, right: 5}, 88 | applyImmediately: NO, 89 | valueBinding: 'OeffiNpc.networkPlanEntryController.name' 90 | }) 91 | }), 92 | 93 | station: SC.View.design({ 94 | layout: {top: 51, left: 10, right: 10, height: 26}, 95 | childViews: 'label name'.w(), 96 | label: SC.LabelView.design({ 97 | layout: {left: 5, width: 70}, 98 | value: 'Station' 99 | }), 100 | name: SC.TextFieldView.design({ 101 | layout: {left: 80, right: 5}, 102 | applyImmediately: NO, 103 | valueBinding: 'OeffiNpc.networkPlanEntryController.stationId' 104 | }) 105 | }), 106 | 107 | position: SC.View.design({ 108 | layout: {top: 92, left: 10, right: 10, height: 26}, 109 | childViews: 'label x y'.w(), 110 | label: SC.LabelView.design({ 111 | layout: {left: 5, width: 70}, 112 | value: 'Position' 113 | }), 114 | x: SC.TextFieldView.design({ 115 | layout: {left: 80, width: 60}, 116 | applyImmediately: NO, 117 | valueBinding: 'OeffiNpc.networkPlanEntryController.x' 118 | }), 119 | y: SC.TextFieldView.design({ 120 | layout: {left: 145, width: 60}, 121 | applyImmediately: NO, 122 | valueBinding: 'OeffiNpc.networkPlanEntryController.y' 123 | }) 124 | }), 125 | 126 | info: SC.View.design({ 127 | layout: {bottom: 10, left: 10, right: 10, top: 128}, 128 | childViews: 'id status'.w(), 129 | id: SC.View.design({ 130 | layout: {left: 10, right: 10, top: 10, height: 26}, 131 | childViews: 'label value'.w(), 132 | label: SC.LabelView.design({ 133 | layout: {left: 0, width: 20}, 134 | value: 'Id:' 135 | }), 136 | value: SC.LabelView.design({ 137 | layout: {left: 25, right: 0}, 138 | valueBinding: 'OeffiNpc.networkPlanEntryController.id' 139 | }) 140 | }), 141 | status: SC.View.design({ 142 | layout: {left: 10, right: 10, top: 36, height: 26}, 143 | childViews: 'label value'.w(), 144 | label: SC.LabelView.design({ 145 | layout: {left: 0, width: 40}, 146 | value: 'Status:' 147 | }), 148 | value: SC.LabelView.design({ 149 | layout: {left: 45, right: 0}, 150 | valueBinding: 'OeffiNpc.networkPlanEntryController.recordStatusString' 151 | }) 152 | }) 153 | }) 154 | }) 155 | }), 156 | 157 | entryView: SC.View.design({ 158 | layout: {left: 320, top: 36, right: 10, bottom: 0}, 159 | childViews: 'imageView bottomView'.w(), 160 | 161 | bottomView: SC.View.design({ 162 | childViews: 'label zoom'.w(), 163 | layout: {left: 150, bottom: 0, height: 36, right: 0}, 164 | 165 | label: SC.LabelView.design({ 166 | layout: {width: 100, left: 0}, 167 | valueBinding: 'OeffiNpc.networkPlanViewController.cursorPositionString' 168 | }), 169 | 170 | zoom: SC.CheckboxView.design({ 171 | layout: {left: 110}, 172 | valueBinding: 'OeffiNpc.networkPlanViewController.zoom' 173 | }) 174 | }), 175 | 176 | imageView: OeffiNpc.NetworkPlanView.design({ 177 | layout: {left: 0, top: 0, right: 0, bottom: 36}, 178 | cursorPositionBinding: 'OeffiNpc.networkPlanViewController.cursorPosition', 179 | valueBinding: 'OeffiNpc.networkPlanController.imageUrl', 180 | zoomBinding: 'OeffiNpc.networkPlanViewController.zoom', 181 | zoomScaleBinding: 'OeffiNpc.networkPlanViewController.zoomScale', 182 | scrollPositionBinding: 'OeffiNpc.networkPlanViewController.scrollPosition' 183 | }) 184 | }) 185 | 186 | }), 187 | 188 | listNetworkPlansPane: SC.Pane.design({ 189 | childViews: 'networkPlanList'.w(), 190 | 191 | networkPlanList: SC.ListView.design({ 192 | layout: {centerX: 0, centerY: 0, width: 250, height: 300}, 193 | showAlternatingRows: YES, 194 | contentBinding: 'OeffiNpc.networkPlansController.arrangedObjects', 195 | selectionBinding: 'OeffiNpc.networkPlanController.content', 196 | contentValueKey: 'networkId', 197 | target: 'OeffiNpc.statechart', 198 | action: 'networkPlanSelected', 199 | actOnSelect: YES 200 | }) 201 | }) 202 | 203 | }); -------------------------------------------------------------------------------- /src/main/sproutcore/apps/oeffi_npc/views/network_plan_view.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: OeffiNpc.NetworkPlanView 3 | // Copyright: @2011 My Company, Inc. 4 | // ========================================================================== 5 | /*globals OeffiNpc */ 6 | 7 | /** @class 8 | 9 | (Document Your View Here) 10 | 11 | @extends SC.ScrollView 12 | */ 13 | OeffiNpc.NetworkPlanView = SC.ScrollView.extend({ 14 | 15 | scrollPositionDidChange: function(){ 16 | var scrollPosition = this.get('scrollPosition'); 17 | var frame = this.get('frame'); 18 | this.scrollTo(scrollPosition.x - (frame.width / 2.0), scrollPosition.y - (frame.height / 2.0)); 19 | this.contentView.highlightPoint(scrollPosition); 20 | }.observes('scrollPosition'), 21 | 22 | valueChanged: function(){ 23 | this.contentView.set('value', this.get('value')); 24 | }.observes('value'), 25 | 26 | zoomChanged: function() { 27 | this.contentView.set('zoom', this.get('zoom')); 28 | }.observes('zoom'), 29 | 30 | zoomScaleChanged: function(){ 31 | this.contentView.set('zoomScale', this.get('zoomScale')); 32 | }.observes('zoomScale'), 33 | 34 | doubleClick: function(evt) { 35 | var point = this.getImageCoords(evt); 36 | OeffiNpc.statechart.sendEvent('clickedOnNetworkPlan', point); 37 | }, 38 | 39 | mouseMoved: function(evt) { 40 | this.set('mouseMoveEvent', evt); 41 | this.contentView.set('mouseMoveEvent', evt); 42 | }, 43 | 44 | mouseMoveEventChanged: function(){ 45 | var evt = this.get('mouseMoveEvent'); 46 | var cursorPosition = this.getImageCoords(evt); 47 | this.set('cursorPosition', cursorPosition); 48 | this.contentView.set('cursorPosition', cursorPosition); 49 | }.observes('mouseMoveEvent'), 50 | 51 | getImageCoords: function(evt) { 52 | var point = { 53 | x: evt.pageX, 54 | y: evt.pageY 55 | }; 56 | 57 | var newFrame = this.convertFrameFromView(point, null); 58 | point.x = newFrame.x; 59 | point.y = newFrame.y; 60 | 61 | var offset = { 62 | x: Math.floor(this.get('horizontalScrollOffset') * this.get('scale')), 63 | y: Math.floor(this.get('verticalScrollOffset') * this.get('scale')) 64 | }; 65 | 66 | point.x += offset.x; 67 | point.y += offset.y; 68 | 69 | return point; 70 | }, 71 | 72 | contentView: SC.ImageView.design(OeffiNpc.ImageViewMixin, { 73 | 74 | from: 20, 75 | to: 60, 76 | cursorPosition: undefined, 77 | prev: undefined, 78 | 79 | highlightPoint: function(point){ 80 | var canvas = this.get('canvas'); 81 | if (!canvas) { 82 | return; 83 | } 84 | 85 | var ctx = canvas.getContext('2d'); 86 | var image = this.get('image'); 87 | this._drawCircle(ctx, image, point, 70, 40); 88 | }, 89 | 90 | _drawCircle: function(ctx, image, center, radius, dt){ 91 | if (radius <= 0.0) { 92 | return; 93 | } 94 | 95 | ctx.save(); 96 | ctx.strokeStyle = 'red'; 97 | ctx.lineWidth = 5.0; 98 | ctx.beginPath(); 99 | ctx.arc(center.x, center.y, radius, 0, Math.PI*2, false); 100 | ctx.stroke(); 101 | ctx.restore(); 102 | var that = this; 103 | setTimeout(function(){ 104 | that._resetArea(ctx, center, radius+10); 105 | that._drawCircle(ctx, image, center, radius-5, dt); 106 | }, dt); 107 | }, 108 | 109 | _resetArea: function(ctx, center, dimension) { 110 | var image = this.get('image'); 111 | var canvas = this.get('canvas'); 112 | var x = Math.max(0, center.x - dimension); 113 | var y = Math.max(0, center.y - dimension); 114 | var w = Math.min(2*dimension, canvas.width - x); 115 | var h = Math.min(2*dimension, canvas.height - y); 116 | ctx.drawImage(image, 117 | x,y,w,h, 118 | x,y,w,h); 119 | }, 120 | 121 | cursorPositionChanged: function(){ 122 | if (this.get('zoom') === YES) { 123 | this.repaint(); 124 | } 125 | }.observes('cursorPosition'), 126 | 127 | zoomPropertiesChanged: function(){ 128 | this.repaint(); 129 | }.observes('zoom', 'zoomScale'), 130 | 131 | mouseEntered: function(evt) { 132 | this.set('canvas', evt.srcElement); 133 | }, 134 | 135 | repaint: function(){ 136 | var canvas = this.get('canvas'); 137 | if (!canvas) { 138 | SC.debug('#repaint :: no canvas available'); 139 | return; 140 | } 141 | 142 | var cursorPosition = this.get('cursorPosition'); 143 | var width = canvas.width; 144 | var height = canvas.height; 145 | 146 | this._resetPreviousZoom(canvas); 147 | if (this.get('zoom') == YES) { 148 | this._drawNewZoom(canvas); 149 | } 150 | }, 151 | 152 | _resetPreviousZoom: function(canvas) { 153 | var ctx = canvas.getContext('2d'); 154 | var image = this.get('image'); 155 | var prev = this.get('prev'); 156 | if (prev) { 157 | try { 158 | var pos = this._calculateMagnifierPosition(canvas, prev); 159 | ctx.drawImage(image, 160 | pos.x-this.to, 161 | pos.y-this.to, 162 | 2*this.to, 163 | 2*this.to, 164 | pos.x-this.to, 165 | pos.y-this.to, 166 | 2*this.to, 167 | 2*this.to); 168 | 169 | } catch (err) { 170 | // silent 171 | } 172 | } 173 | }, 174 | 175 | _calculateMagnifierPosition: function(canvas, pos) { 176 | var x = Math.min(Math.max(this.to, pos.x), canvas.width - this.to); 177 | var y = Math.min(Math.max(this.to, pos.y), canvas.height - this.to); 178 | return { 179 | x: x, 180 | y: y, 181 | topLeft: { 182 | x: x - this.to, 183 | y: y - this.to 184 | }, 185 | width: 2*this.to, 186 | height: 2*this.to 187 | }; 188 | }, 189 | 190 | _calculateMagnifierRect: function(canvas, center, magnifierPos, srcWidth, targetWidth) { 191 | var src = { 192 | x: Math.max(center.x - srcWidth, 0), 193 | y: Math.max(center.y - srcWidth, 0), 194 | width: 2*srcWidth, 195 | height: 2*srcWidth 196 | }; 197 | var target = { 198 | x: Math.max(center.x - targetWidth, 0), 199 | y: Math.max(center.y - targetWidth, 0), 200 | width: 2*targetWidth, 201 | height: 2*targetWidth 202 | }; 203 | 204 | src.width = Math.min(src.width, (canvas.width - src.x)); 205 | src.height = Math.min(src.height, (canvas.height - src.y)); 206 | 207 | target.width = Math.min(target.width, (canvas.width - target.x)); 208 | target.height = Math.min(target.height, (canvas.height - target.y)); 209 | 210 | target.x = target.x + (magnifierPos.x - center.x); 211 | target.y = target.y + (magnifierPos.y - center.y); 212 | 213 | return { 214 | src: src, 215 | target: target 216 | }; 217 | }, 218 | 219 | _drawNewZoom: function(canvas){ 220 | var ctx = canvas.getContext('2d'); 221 | var cursorPosition = this.get('cursorPosition'); 222 | var image = this.get('image'); 223 | var magnifierPos = this._calculateMagnifierPosition(canvas, cursorPosition); 224 | 225 | ctx.save(); 226 | 227 | ctx.beginPath(); 228 | ctx.arc(magnifierPos.x, magnifierPos.y, this.to, 0, Math.PI*2, false); 229 | ctx.clip(); 230 | 231 | ctx.fillStyle = 'white'; 232 | ctx.fillRect(magnifierPos.topLeft.x, magnifierPos.topLeft.y, magnifierPos.width, magnifierPos.height); 233 | 234 | var w = this.to/this.zoomScale; 235 | var magnifierRect = this._calculateMagnifierRect(canvas, cursorPosition, magnifierPos, w, this.to); 236 | ctx.drawImage(image, 237 | magnifierRect.src.x, 238 | magnifierRect.src.y, 239 | magnifierRect.src.width, 240 | magnifierRect.src.height, 241 | magnifierRect.target.x, 242 | magnifierRect.target.y, 243 | magnifierRect.target.width, 244 | magnifierRect.target.height); 245 | 246 | this._drawCrosshair(ctx, magnifierPos); 247 | this._drawMagnifierCircle(ctx, magnifierPos); 248 | 249 | ctx.restore(); 250 | 251 | this.set('prev', { 252 | x: magnifierPos.x, 253 | y: magnifierPos.y 254 | }); 255 | }, 256 | 257 | _drawMagnifierCircle: function(ctx, center){ 258 | ctx.save(); 259 | ctx.strokeStyle = 'black'; 260 | ctx.lineWidth = 3.0; 261 | ctx.beginPath(); 262 | ctx.arc(center.x, center.y, this.to, 0, Math.PI*2, false); 263 | ctx.stroke(); 264 | ctx.restore(); 265 | }, 266 | 267 | _drawCrosshair: function(ctx, center, dimension, thickness) { 268 | dimension = dimension || this.to; 269 | thickness = thickness || 1.0; 270 | ctx.save(); 271 | ctx.beginPath(); 272 | ctx.strokeStyle = 'red'; 273 | ctx.lineWidth = thickness; 274 | ctx.moveTo(center.x, center.y-dimension); 275 | ctx.lineTo(center.x, center.y+dimension); 276 | ctx.moveTo(center.x-dimension, center.y); 277 | ctx.lineTo(center.x+dimension, center.y); 278 | ctx.stroke(); 279 | 280 | ctx.restore(); 281 | } 282 | }) 283 | }); 284 | -------------------------------------------------------------------------------- /src/test/resources/bonn_schnellverkehr.csv: -------------------------------------------------------------------------------- 1 | # "networkId"|"stationId"|"name"|"planId"|"x"|"y" 2 | "vrr"|22000687|"Bonn Hbf"|"bonn_schnellbahn"|1270|770 3 | "vrr"|22001104|"Stadthaus"|"bonn_schnellbahn"|1275|645 4 | "vrr"|22001115|"B.-v.-Suttner-Pl./Beethovenhaus"|"bonn_schnellbahn"|1375|645 5 | "vrr"|22001500|"Konrad-Adenauer-Platz"|"bonn_schnellbahn"|1510|645 6 | "vrr"|22001512|"Adelheidisstr."|"bonn_schnellbahn"|1535|640 7 | "vrr"|22001522|"Vilich"|"bonn_schnellbahn"|1555|625 8 | "vrr"|22001521|"Vilich-Müldorf"|"bonn_schnellbahn"|1590|590 9 | "vrr"|22001800|"Hangelar West"|"bonn_schnellbahn"|1620|560 10 | "vrr"|22001801|"Hangelar Mitte"|"bonn_schnellbahn"|1650|530 11 | "vrr"|22001802|"Hangelar Ost"|"bonn_schnellbahn"|1675|500 12 | "vrr"|22001803|"Sankt Augustin Ort"|"bonn_schnellbahn"|1685|465 13 | "vrr"|22001805|"Sankt Augustin Kloster"|"bonn_schnellbahn"|1685|425 14 | "vrr"|22001806|"Sankt Augustin Markt"|"bonn_schnellbahn"|1685|385 15 | "vrr"|22001807|"Sankt Augustin-Mülldorf"|"bonn_schnellbahn"|1675|350 16 | "vrr"|22001811|"Siegburg"|"bonn_schnellbahn"|1630|295 17 | "vrr"|22000686|"Universität/Markt"|"bonn_schnellbahn"|1320|800 18 | "vrr"|22000685|"Juridicum"|"bonn_schnellbahn"|1360|835 19 | "vrr"|22000684|"Bundesrechnungshof/Auswärtiges Amt"|"bonn_schnellbahn"|1395|875 20 | "vrr"|22000683|"Museum Koenig"|"bonn_schnellbahn"|1435|915 21 | "vrr"|22000692|"Heussallee/Museumsmeile"|"bonn_schnellbahn"|1475|950 22 | "vrr"|22000690|"Deutsche Telekom/Ollenhauerstr."|"bonn_schnellbahn"|1510|985 23 | "vrr"||"Deutsche Telekom/Olof-Palme-Allee"|"bonn_schnellbahn"|1540|1010 24 | "vrr"|22001655|"Robert-Schuman-Platz"|"bonn_schnellbahn"|1615|970 25 | "vrr"|22001268|"Rheinaue"|"bonn_schnellbahn"|1625|900 26 | "vrr"|22001584|"Ramersdorf"|"bonn_schnellbahn"|1760|840 27 | "vrr"|22001591|"Oberkassel Nord"|"bonn_schnellbahn"|1800|880 28 | "vrr"|22001592|"Oberkassel Mitte"|"bonn_schnellbahn"|1835|920 29 | "vrr"|22001593|"Oberkassel Süd/Römlinghoven"|"bonn_schnellbahn"|1875|955 30 | "vrr"|22001770|"Oberdollendorf Nord"|"bonn_schnellbahn"|1910|990 31 | "vrr"|22001774|"Oberdollendorf"|"bonn_schnellbahn"|1930|1030 32 | "vrr"|22001777|"Longenburg"|"bonn_schnellbahn"|1895|1070 33 | "vrr"|22001778|"Königswinter Clemens-August-Str."|"bonn_schnellbahn"|1900|1105 34 | "vrr"|22001779|"Königsw. Fähre/Sea Life Aquarium"|"bonn_schnellbahn"|1930|1135 35 | "vrr"|22001776|"Königsw. Denkmal"|"bonn_schnellbahn"|1955|1165 36 | "vrr"|22001781|"Rhöndorf"|"bonn_schnellbahn"|1995|1180 37 | "vrr"|22002737|"Am Spitzenbach"|"bonn_schnellbahn"|2010|1215 38 | "vrr"|22001788|"Bad Honnef"|"bonn_schnellbahn"|2035|1245 39 | "vrr"|22000698|"Max-Löbner-Str./Friesdorf"|"bonn_schnellbahn"|1560|1045 40 | "vrr"|22000699|"Hochkreuz/Deutsches Museum Bonn"|"bonn_schnellbahn"|1585|1065 41 | "vrr"|22000700|"Wurzerstr."|"bonn_schnellbahn"|1625|1100 42 | "vrr"|22000701|"Plittersdorfer Str."|"bonn_schnellbahn"|1645|1140 43 | "vrr"|22001602|"Bad Godesberg Bf"|"bonn_schnellbahn"|1655|1210 44 | "vrr"|22007185|"Bad Godesberg Stadthalle"|"bonn_schnellbahn"|1640|1275 45 | "vrr"|22001143|"Poppelsdorfer Allee"|"bonn_schnellbahn"|1290|865 46 | "vrr"|22001130|"Königstr."|"bonn_schnellbahn"|1290|900 47 | "vrr"|22001131|"Weberstr."|"bonn_schnellbahn"|1290|935 48 | "vrr"|22001132|"Rittershausstr."|"bonn_schnellbahn"|1290|970 49 | "vrr"|22001252|"Haus der Jugend"|"bonn_schnellbahn"|1290|1005 50 | "vrr"|22001253|"Eduard-Otto-Str."|"bonn_schnellbahn"|1290|1040 51 | "vrr"|22001270|"Pützstr."|"bonn_schnellbahn"|1300|1080 52 | "vrr"|22001273|"Bergstr."|"bonn_schnellbahn"|1325|1100 53 | "vrr"|22001280|"Hindenburgplatz"|"bonn_schnellbahn"|1350|1125 54 | "vrr"|22001286|"Dottendorf Quirinusplatz"|"bonn_schnellbahn"|1380|1155 55 | "vrr"|22001106|"Th-Mann-Str."|"bonn_schnellbahn"|1290|705 56 | "vrr"|22001110|"Wilhelmsplatz"|"bonn_schnellbahn"|1290|610 57 | "vrr"|22001162|"Rosental"|"bonn_schnellbahn"|1290|580 58 | "vrr"|22001182|"Chlodwigplatz"|"bonn_schnellbahn"|1290|555 59 | "vrr"|22001175|"LVR-Klinik"|"bonn_schnellbahn"|1290|525 60 | "vrr"|22001176|"Finanzministerium"|"bonn_schnellbahn"|1290|495 61 | "vrr"|22001177|"Innenministerium"|"bonn_schnellbahn"|1290|465 62 | "vrr"|22001178|"Heinrich-Hertz-Europakolleg"|"bonn_schnellbahn"|1290|435 63 | "vrr"|22008437|"An der Josefshöhe"|"bonn_schnellbahn"|1290|410 64 | "vrr"|22008507|"Pariser Str."|"bonn_schnellbahn"|1290|380 65 | "vrr"|22001406|"Auerberg Kopenhagener Str."|"bonn_schnellbahn"|1290|350 66 | "vrr"|22001501|"Beuel Rathaus"|"bonn_schnellbahn"|1530|695 67 | "vrr"|22001503|"Obere Wilhelmstr."|"bonn_schnellbahn"|1550|720 68 | "vrr"|22001504|"Beuel Bf"|"bonn_schnellbahn"|1585|735 69 | "vrr"|22008471|"Limperich Nord"|"bonn_schnellbahn"|1610|770 70 | "vrr"|22001586|"Limperich"|"bonn_schnellbahn"|1650|770 71 | "vrr"|22001585|"Küdinghoven"|"bonn_schnellbahn"|1700|775 72 | "vrr"|22008950|"Schießbergweg"|"bonn_schnellbahn"|1735|800 73 | "vrr"|22000688|"BN West"|"bonn_schnellbahn"|1190|690 74 | "vrr"|22000689|"Brühler Str."|"bonn_schnellbahn"|1075|655 75 | "vrr"|22000696|"Robert-Kirchhoff-Str."|"bonn_schnellbahn"|1020|655 76 | "vrr"|22000697|"Dransdorf"|"bonn_schnellbahn"|965|655 77 | "vrr"|22000681|"Alfter"|"bonn_schnellbahn"|925|640 78 | "vrr"|22000672|"Roisdorf West"|"bonn_schnellbahn"|905|620 79 | "vrr"||"Bornheim Rathaus"|"bonn_schnellbahn"|885|600 80 | "vrr"|22000673|"Bornheim"|"bonn_schnellbahn"|865|580 81 | "vrr"|22000674|"Dersdorf"|"bonn_schnellbahn"|840|555 82 | "vrr"|22000675|"Waldorf"|"bonn_schnellbahn"|810|530 83 | "vrr"|22000676|"Merten"|"bonn_schnellbahn"|780|500 84 | "vrr"|22000677|"Walberberg"|"bonn_schnellbahn"|755|475 85 | "vrr"|22000739|"Brühl-Schwadorf"|"bonn_schnellbahn"|730|450 86 | "vrr"|22000737|"Brühl-Badorf"|"bonn_schnellbahn"|700|425 87 | "vrr"|22000736|"Brühl Süd"|"bonn_schnellbahn"|680|395 88 | "vrr"|22000735|"Brühl Mitte"|"bonn_schnellbahn"|650|365 89 | "vrr"|22000905|"Propsthof Nord"|"bonn_schnellbahn"|1155|635 90 | "vrr"|22000693|"Tannenbusch Süd"|"bonn_schnellbahn"|1140|585 91 | "vrr"|22000694|"Tannenbusch Mitte"|"bonn_schnellbahn"|1140|530 92 | "vrr"|22000695|"Buschdorf"|"bonn_schnellbahn"|1140|485 93 | "vrr"|22000678|"Hersel"|"bonn_schnellbahn"|1140|450 94 | "vrr"|22000679|"Uedorf"|"bonn_schnellbahn"|1140|415 95 | "vrr"|22000680|"Widdig"|"bonn_schnellbahn"|1140|385 96 | "vrr"|22000743|"Urfeld"|"bonn_schnellbahn"|1140|350 97 | "vrr"|22000740|"Wesseling Süd"|"bonn_schnellbahn"|1140|315 98 | "vrr"|22000741|"Wesseling"|"bonn_schnellbahn"|1140|285 99 | "vrr"|22000742|"Wesseling Nord"|"bonn_schnellbahn"|1140|250 100 | "vrr"|22000134|"Godorf"|"bonn_schnellbahn"|1140|215 101 | "vrr"|22000124|"Sürth"|"bonn_schnellbahn"|1140|185 102 | "vrr"|22000468|"Porz"|"bonn_schnellbahn"|1365|140 103 | "vrr"|22000488|"Porz-Wahn"|"bonn_schnellbahn"|1450|205 104 | "vrr"|22002030|"Spich"|"bonn_schnellbahn"|1480|240 105 | "vrr"|22002071|"Troisdorf"|"bonn_schnellbahn"|1515|280 106 | "vrr"|22002243|"Hennef"|"bonn_schnellbahn"|1700|285 107 | "vrr"|22009664|"Blankenberg"|"bonn_schnellbahn"|1755|275 108 | "vrr"|22009665|"Merten"|"bonn_schnellbahn"|1810|275 109 | "vrr"|22002461|"Eitorf"|"bonn_schnellbahn"|1870|285 110 | "vrr"|22004688|"Herchen"|"bonn_schnellbahn"|1925|285 111 | "vrr"|22004861|"Dattenfeld"|"bonn_schnellbahn"|1985|285 112 | "vrr"|22004728|"Schladern"|"bonn_schnellbahn"|2040|285 113 | "vrr"|22004723|"Rosbach"|"bonn_schnellbahn"|2095|285 114 | "vrr"|22004719|"Au (Sieg)"|"bonn_schnellbahn"|2155|290 115 | "vrr"|22000999|"Köln/Bonn Flughafen"|"bonn_schnellbahn"|1575|120 116 | "vrr"|22002066|"Friedrich-Wilhelmshütte"|"bonn_schnellbahn"|1575|395 117 | "vrr"|22001823|"Menden"|"bonn_schnellbahn"|1575|490 118 | "vrr"|22009624|"BN-Oberkassel"|"bonn_schnellbahn"|1770|925 119 | "vrr"|22002179|"Niederdollendorf"|"bonn_schnellbahn"|1860|1010 120 | "vrr"|22002182|"Königswinter"|"bonn_schnellbahn"|1950|1105 121 | "vrr"|22001797|"Bad Honnef"|"bonn_schnellbahn"|2060|1215 122 | "vrr"|22001762|"BN-Mehlem "|"bonn_schnellbahn"|1745|1285 123 | "vrr"|22009601|"Roisdorf"|"bonn_schnellbahn"|1020|560 124 | "vrr"|22005351|"Sechtem"|"bonn_schnellbahn"|915|460 125 | "vrr"|22005502|"Brühl"|"bonn_schnellbahn"|835|380 126 | "vrr"|22001341|"BN-Duisdorf"|"bonn_schnellbahn"|1070|740 127 | "vrr"|22009599|"Witterschlick"|"bonn_schnellbahn"|1025|785 128 | "vrr"|22009598|"Kottenforst"|"bonn_schnellbahn"|980|835 129 | "vrr"|22008845|"Meckenheim Industriepark"|"bonn_schnellbahn"|915|870 130 | "vrr"|22001489|"Meckenheim"|"bonn_schnellbahn"|830|870 131 | "vrr"|22001690|"Rheinbach"|"bonn_schnellbahn"|745|870 132 | "vrr"|22001857|"Odendorf"|"bonn_schnellbahn"|660|870 133 | "vrr"|22009596|"Kuchenheim"|"bonn_schnellbahn"|575|870 134 | "vrr"|22006058|"Euskirchen"|"bonn_schnellbahn"|475|865 135 | "vrr"|22006055|"Zuckerfabrik"|"bonn_schnellbahn"|510|910 136 | "vrr"|22009592|"Stotzheim"|"bonn_schnellbahn"|510|970 137 | "vrr"|22009593|"Kreuzweingarten"|"bonn_schnellbahn"|510|1025 138 | "vrr"|22006140|"Arloff"|"bonn_schnellbahn"|510|1080 139 | "vrr"|22006137|"Iversheim"|"bonn_schnellbahn"|510|1135 140 | "vrr"|22006099|"Bad Münstereifel"|"bonn_schnellbahn"|510|1190 141 | "vrr"|22006472|"Dahlem"|"bonn_schnellbahn"|85|1270 142 | "vrr"|22009589|"Schmidtheim"|"bonn_schnellbahn"|55|1220 143 | "vrr"|22006429|"Blankenheim (Wald)"|"bonn_schnellbahn"|80|1170 144 | "vrr"|22006381|"Nettersheim"|"bonn_schnellbahn"|125|1125 145 | "vrr"|22006484|"Urft (Steinfeld)"|"bonn_schnellbahn"|170|1080 146 | "vrr"|22006346|"Kall"|"bonn_schnellbahn"|210|1040 147 | "vrr"|22009588|"Scheven"|"bonn_schnellbahn"|265|985 148 | "vrr"|22006263|"Mechernich"|"bonn_schnellbahn"|320|930 149 | "vrr"|22009587|"Satzvey"|"bonn_schnellbahn"|375|875 150 | "vrr"|22006230|"Großbüllesheim"|"bonn_schnellbahn"|510|765 151 | "vrr"|22009585|"Derkum"|"bonn_schnellbahn"|510|670 152 | "vrr"|22006221|"Weilerswist"|"bonn_schnellbahn"|510|575 153 | "vrr"|22006913|"Erftstadt"|"bonn_schnellbahn"|510|475 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.pangratz 8 | oeffi-networkplan-curator 9 | 0.1-SNAPSHOT 10 | war 11 | 12 | oeffinpc 13 | 14 | 15 | 16 | maven-restlet 17 | Public online Restlet repository 18 | http://maven.restlet.org 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | commons-httpclient 27 | commons-httpclient 28 | 3.1 29 | test 30 | 31 | 32 | 33 | 34 | net.sf.opencsv 35 | opencsv 36 | 2.1 37 | 38 | 39 | 40 | 41 | org.restlet.gae 42 | org.restlet 43 | 2.0.8 44 | 45 | 46 | org.restlet.gae 47 | org.restlet.ext.gae 48 | 2.1-SNAPSHOT 49 | 50 | 51 | org.restlet.gae 52 | org.restlet.ext.servlet 53 | 2.0.8 54 | 55 | 56 | org.restlet.gae 57 | org.restlet.ext.json 58 | 2.0.8 59 | 60 | 61 | org.restlet.gae 62 | org.restlet.ext.freemarker 63 | 2.0.8 64 | 65 | 66 | org.restlet.gae 67 | org.restlet.ext.fileupload 68 | 2.1-SNAPSHOT 69 | 70 | 71 | org.apache.commons 72 | commons-io 73 | 1.3.2 74 | 75 | 76 | 77 | 78 | net.kindleit 79 | gae-runtime 80 | ${gae.version} 81 | pom 82 | 83 | 84 | 85 | org.datanucleus 86 | datanucleus-core 87 | ${datanucleus.version} 88 | runtime 89 | 90 | 91 | 92 | javax.transaction 93 | jta 94 | 1.1 95 | 96 | 97 | 99 | 100 | org.apache.geronimo.specs 101 | geronimo-servlet_2.5_spec 102 | 1.2 103 | provided 104 | 105 | 106 | 107 | 108 | standard 109 | taglibs 110 | 1.1.2 111 | jar 112 | runtime 113 | 114 | 115 | 116 | 117 | org.slf4j 118 | slf4j-api 119 | 1.6.1 120 | 121 | 122 | 123 | ch.qos.logback 124 | logback-classic 125 | 0.9.24 126 | 127 | 128 | 129 | 130 | junit 131 | junit 132 | 4.5 133 | test 134 | 135 | 136 | 137 | 138 | com.google.appengine 139 | appengine-api-labs 140 | ${gae.version} 141 | test 142 | 143 | 144 | 145 | com.google.appengine 146 | appengine-api-stubs 147 | ${gae.version} 148 | test 149 | 150 | 151 | 152 | com.google.appengine 153 | appengine-testing 154 | ${gae.version} 155 | test 156 | 157 | 158 | 159 | javax.jdo 160 | jdo2-api 161 | 2.3-eb 162 | 163 | 164 | javax.transaction 165 | transaction-api 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 175 | 176 | org.datanucleus 177 | maven-datanucleus-plugin 178 | 1.1.4 179 | 180 | 181 | **/model/*.class 182 | true 183 | ASM 184 | JDO 185 | 186 | 187 | 188 | compile 189 | 190 | enhance 191 | 192 | 193 | 194 | 195 | 196 | org.datanucleus 197 | datanucleus-core 198 | ${datanucleus.version} 199 | 200 | 201 | javax.transaction 202 | transaction-api 203 | 204 | 205 | 206 | 207 | org.datanucleus 208 | datanucleus-rdbms 209 | ${datanucleus.version} 210 | 211 | 212 | org.datanucleus 213 | datanucleus-enhancer 214 | 1.1.4 215 | 216 | 217 | javax.jdo 218 | jdo2-api 219 | 2.3-ec 220 | runtime 221 | 222 | 223 | 224 | 225 | 226 | org.apache.maven.plugins 227 | maven-war-plugin 228 | 2.1-beta-1 229 | 230 | 231 | 232 | src/main/webapp 233 | true 234 | 235 | **/appengine-web.xml 236 | 237 | 238 | 239 | 240 | 241 | 242 | src/main/sproutcore/tmp/build 243 | 244 | 245 | 246 | 247 | 248 | 250 | 251 | net.kindleit 252 | maven-gae-plugin 253 | 0.8.4 254 | 255 | 256 | net.kindleit 257 | gae-runtime 258 | ${gae.version} 259 | pom 260 | 261 | 262 | 263 | appengine.google.com 264 | 265 | 266 | 267 | 268 | 269 | maven-release-plugin 270 | 271 | gae:deploy 272 | 273 | 274 | 275 | 276 | 277 | org.apache.maven.plugins 278 | maven-compiler-plugin 279 | 2.0 280 | 281 | 1.6 282 | 1.6 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | UTF-8 294 | 295 | 297 | true 298 | 299 | 301 | 1.5.1 302 | 303 | 304 | test 305 | 306 | 1.1.5 307 | 308 | 309 | 310 | 313 | 314 | integration-build 315 | 316 | stage 317 | 318 | 319 | 320 | 324 | 325 | release-build 326 | 327 | 328 | performRelease 329 | true 330 | 331 | 332 | 333 | 334 | 336 | release 337 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /src/test/java/com/pangratz/oeffinpc/model/ModelUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.pangratz.oeffinpc.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | import javax.jdo.PersistenceManager; 9 | import javax.jdo.Query; 10 | 11 | import junit.framework.TestCase; 12 | 13 | import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; 14 | import com.google.appengine.tools.development.testing.LocalServiceTestHelper; 15 | 16 | public class ModelUtilsTest extends TestCase { 17 | 18 | private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()); 19 | private ModelUtils modelUtils = null; 20 | private PersistenceManager pm; 21 | 22 | public void testAddInvalidNetworkPlanEntries() { 23 | assertEquals(0, modelUtils.storeNetworkPlanEntries(null, null)); 24 | assertEquals(0, modelUtils.storeNetworkPlanEntries(1L, null)); 25 | assertEquals(0, modelUtils.storeNetworkPlanEntries(null, new LinkedList())); 26 | } 27 | 28 | public void testAddNetworkPlanAndEntries() { 29 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 30 | Long linzKey = modelUtils.storeNetworkPlan(linz); 31 | 32 | NetworkPlanEntry schumpeter = createNetworkPlanEntry(linzKey, "Schumpeterstrasse", "123", 100, 200); 33 | Long schumpeterKey = modelUtils.storeNetworkPlanEntry(schumpeter); 34 | assertNotNull(schumpeterKey); 35 | NetworkPlanEntry insertedSchumpeter = modelUtils.getNetworkPlanEntry(schumpeterKey); 36 | assertNotNull(insertedSchumpeter); 37 | assertEquals(schumpeterKey, insertedSchumpeter.getKey()); 38 | assertEquals(linzKey, insertedSchumpeter.getNetworkPlanKey()); 39 | assertEquals("Schumpeterstrasse", insertedSchumpeter.getName()); 40 | assertEquals("123", insertedSchumpeter.getStationId()); 41 | assertEquals(100, insertedSchumpeter.getX()); 42 | assertEquals(200, insertedSchumpeter.getY()); 43 | 44 | NetworkPlanEntry hauptbahnhof = createNetworkPlanEntry(linzKey, "Hauptbahnhof", "456", 189, 567); 45 | Long hauptbahnhofKey = modelUtils.storeNetworkPlanEntry(hauptbahnhof); 46 | assertNotNull(hauptbahnhofKey); 47 | NetworkPlanEntry insertedHauptbahnhof = modelUtils.getNetworkPlanEntry(hauptbahnhofKey); 48 | assertNotNull(insertedHauptbahnhof); 49 | assertEquals(hauptbahnhofKey, insertedHauptbahnhof.getKey()); 50 | assertEquals(linzKey, insertedHauptbahnhof.getNetworkPlanKey()); 51 | assertEquals("Hauptbahnhof", insertedHauptbahnhof.getName()); 52 | assertEquals("456", insertedHauptbahnhof.getStationId()); 53 | assertEquals(189, insertedHauptbahnhof.getX()); 54 | assertEquals(567, insertedHauptbahnhof.getY()); 55 | 56 | List networkPlanEntries = modelUtils.getNetworkPlanEntries(linzKey); 57 | assertNotNull(networkPlanEntries); 58 | assertEquals(2, networkPlanEntries.size()); 59 | 60 | NetworkPlanEntry hauptbahnhofEntry = networkPlanEntries.get(0); 61 | assertEquals(hauptbahnhof.getName(), hauptbahnhofEntry.getName()); 62 | 63 | NetworkPlanEntry schumpeterEntry = networkPlanEntries.get(1); 64 | assertEquals(schumpeter.getName(), schumpeterEntry.getName()); 65 | } 66 | 67 | public void testAddNetworkPlanEntries() { 68 | NetworkPlan bonn = createNetworkPlan("bonn", "bonn", "http://oeffi.schildbach.de/plans/bonn.png"); 69 | Long bonnKey = modelUtils.storeNetworkPlan(bonn); 70 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(bonnKey, "Hauptbahnhof", "90", 60, 90)); 71 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(bonnKey, "Bla Bla", "91", 59, 90)); 72 | 73 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 74 | Long linzKey = modelUtils.storeNetworkPlan(linz); 75 | 76 | NetworkPlanEntry hbf = createNetworkPlanEntry(null, "Hauptbahnhof", "123", 0, 20); 77 | NetworkPlanEntry schumpeter = createNetworkPlanEntry(null, "Schumpeterstrasse", "222", 0, 0); 78 | NetworkPlanEntry dornach = createNetworkPlanEntry(null, "Dornach", "333", 0, 0); 79 | List entries = Arrays.asList(hbf, schumpeter, dornach); 80 | int nrOfStations = modelUtils.storeNetworkPlanEntries(linzKey, entries); 81 | assertEquals(3, nrOfStations); 82 | 83 | List linzEntries = modelUtils.getNetworkPlanEntries(linzKey); 84 | assertNotNull(linzEntries); 85 | assertEquals(3, linzEntries.size()); 86 | } 87 | 88 | public void testAddTwoNetworkPlans() { 89 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 90 | Long linzKey = modelUtils.storeNetworkPlan(linz); 91 | 92 | NetworkPlan bonn = createNetworkPlan("bonn", "bonn", "http://oeffi.schildbach.de/plans/bonn.png"); 93 | Long bonnKey = modelUtils.storeNetworkPlan(bonn); 94 | 95 | assertFalse(linzKey.equals(bonnKey)); 96 | } 97 | 98 | public void testEditNetworkPlan() { 99 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 100 | modelUtils.storeNetworkPlan(linz); 101 | 102 | linz.setImageUrl("http://oeffi.schildbach.de/plans/linz_bw.png"); 103 | Long key = modelUtils.storeNetworkPlan(linz); 104 | 105 | NetworkPlan insertedNetworkPlan = pm.getObjectById(NetworkPlan.class, key); 106 | assertNotNull(insertedNetworkPlan); 107 | assertEquals("http://oeffi.schildbach.de/plans/linz_bw.png", insertedNetworkPlan.getImageUrl()); 108 | assertEquals("linz", insertedNetworkPlan.getNetworkId()); 109 | assertEquals("linz", insertedNetworkPlan.getPlanId()); 110 | } 111 | 112 | public void testEditNetworkPlanEntry() { 113 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 114 | Long linzKey = modelUtils.storeNetworkPlan(linz); 115 | 116 | NetworkPlanEntry schumpeter = createNetworkPlanEntry(linzKey, "Schumpeterstrasse", "123", 100, 200); 117 | Long schumpeterKey = modelUtils.storeNetworkPlanEntry(schumpeter); 118 | 119 | NetworkPlanEntry editedSchumpeter = createNetworkPlanEntry(linzKey, "Schumpeterstr.", "321", 111, 222); 120 | editedSchumpeter.setKey(schumpeterKey); 121 | Long editedSchumpeterKey = modelUtils.storeNetworkPlanEntry(editedSchumpeter); 122 | 123 | assertEquals(schumpeterKey, editedSchumpeterKey); 124 | NetworkPlanEntry insertedSchumpeter = modelUtils.getNetworkPlanEntry(schumpeterKey); 125 | assertNotNull(insertedSchumpeter); 126 | assertEquals("Schumpeterstr.", insertedSchumpeter.getName()); 127 | assertEquals("321", insertedSchumpeter.getStationId()); 128 | assertEquals(111, insertedSchumpeter.getX()); 129 | assertEquals(222, insertedSchumpeter.getY()); 130 | } 131 | 132 | public void testGetInvalidNetworkPlan() { 133 | assertNull(modelUtils.getNetworkPlan(null)); 134 | assertNull(modelUtils.getNetworkPlan(1L)); 135 | } 136 | 137 | public void testGetInvalidNetworkPlanEntries() { 138 | List nullEntries = modelUtils.getNetworkPlanEntries(null); 139 | assertNotNull(nullEntries); 140 | assertEquals(0, nullEntries.size()); 141 | 142 | List invalidEntries = modelUtils.getNetworkPlanEntries(Long.MIN_VALUE); 143 | assertNotNull(invalidEntries); 144 | assertEquals(0, invalidEntries.size()); 145 | } 146 | 147 | public void testGetInvalidNetworkPlanEntry() { 148 | assertNull(modelUtils.getNetworkPlanEntry(null)); 149 | assertNull(modelUtils.getNetworkPlanEntry(1L)); 150 | } 151 | 152 | public void testGetNetworkPlan() { 153 | NetworkPlan bonn = createNetworkPlan("bonn", "bonn", "http://oeffi.schildbach.de/plans/bonn.png"); 154 | modelUtils.storeNetworkPlan(bonn); 155 | 156 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 157 | Long linzKey = modelUtils.storeNetworkPlan(linz); 158 | 159 | NetworkPlan insertedNetworkPlan = modelUtils.getNetworkPlan(linzKey); 160 | assertNotNull(insertedNetworkPlan); 161 | assertEquals("linz", insertedNetworkPlan.getNetworkId()); 162 | assertEquals("linz", insertedNetworkPlan.getPlanId()); 163 | assertEquals("http://oeffi.schildbach.de/plans/linz.png", insertedNetworkPlan.getImageUrl()); 164 | assertEquals(linzKey, insertedNetworkPlan.getKey()); 165 | } 166 | 167 | public void testGetNetworkPlanEntries() { 168 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 169 | Long linzKey = modelUtils.storeNetworkPlan(linz); 170 | 171 | List entries = modelUtils.getNetworkPlanEntries(linzKey); 172 | assertNotNull(entries); 173 | assertEquals(0, entries.size()); 174 | 175 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(linzKey, "Hauptbahnhof", "111", 100, 200)); 176 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(linzKey, "Schumpeterstrasse", "222", 111, 2222)); 177 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(linzKey, "Rudolfstrasse", "333", 333, 444)); 178 | 179 | NetworkPlan bonn = createNetworkPlan("bonn", "bonn", "http://oeffi.schildbach.de/plans/bonn.png"); 180 | Long bonnKey = modelUtils.storeNetworkPlan(bonn); 181 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(bonnKey, "Bonn Station 1", "1", 1, 1)); 182 | modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(bonnKey, "Bonn Station 2", "2", 2, 2)); 183 | 184 | List linzNetworkPlanEntries = modelUtils.getNetworkPlanEntries(linzKey); 185 | assertNotNull(linzNetworkPlanEntries); 186 | assertEquals(3, linzNetworkPlanEntries.size()); 187 | 188 | List bonnNetworkPlanEntries = modelUtils.getNetworkPlanEntries(bonnKey); 189 | assertNotNull(bonnNetworkPlanEntries); 190 | assertEquals(2, bonnNetworkPlanEntries.size()); 191 | } 192 | 193 | public void testGetNetworkPlans() { 194 | List networkPlans = modelUtils.getNetworkPlans(); 195 | assertEquals(0, networkPlans.size()); 196 | 197 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 198 | modelUtils.storeNetworkPlan(linz); 199 | assertEquals(1, modelUtils.getNetworkPlans().size()); 200 | 201 | NetworkPlan bonn = createNetworkPlan("bonn", "bonn", "http://oeffi.schildbach.de/plans/bonn.png"); 202 | modelUtils.storeNetworkPlan(bonn); 203 | assertEquals(2, modelUtils.getNetworkPlans().size()); 204 | } 205 | 206 | public void testInsertNewNetworkPlan() { 207 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 208 | Long key = modelUtils.storeNetworkPlan(linz); 209 | assertNotNull(key); 210 | 211 | NetworkPlan insertedNetworkPlan = pm.getObjectById(NetworkPlan.class, key); 212 | assertNotNull(insertedNetworkPlan); 213 | assertEquals("http://oeffi.schildbach.de/plans/linz.png", insertedNetworkPlan.getImageUrl()); 214 | assertEquals("linz", insertedNetworkPlan.getNetworkId()); 215 | assertEquals("linz", insertedNetworkPlan.getPlanId()); 216 | } 217 | 218 | public void testRemoveInvalidNetworkPlanEntries() { 219 | assertEquals(0, modelUtils.removeNetworkPlanEntries(null)); 220 | assertEquals(0, modelUtils.removeNetworkPlanEntries(1L)); 221 | } 222 | 223 | public void testRemoveNetworkPlanEntries() { 224 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 225 | Long linzKey = modelUtils.storeNetworkPlan(linz); 226 | 227 | NetworkPlanEntry hbf = createNetworkPlanEntry(null, "Hauptbahnhof", "123", 0, 20); 228 | NetworkPlanEntry schumpeter = createNetworkPlanEntry(null, "Schumpeterstrasse", "222", 0, 0); 229 | NetworkPlanEntry dornach = createNetworkPlanEntry(null, "Dornach", "333", 0, 0); 230 | List entries = Arrays.asList(hbf, schumpeter, dornach); 231 | modelUtils.storeNetworkPlanEntries(linzKey, entries); 232 | 233 | long removedNetworkPlanEntriesCount = modelUtils.removeNetworkPlanEntries(linzKey); 234 | assertEquals(3, removedNetworkPlanEntriesCount); 235 | 236 | // network plan is still here? 237 | NetworkPlan networkPlan = modelUtils.getNetworkPlan(linzKey); 238 | assertNotNull(networkPlan); 239 | assertEquals("linz", networkPlan.getNetworkId()); 240 | } 241 | 242 | public void testRemoveNetworkPlanEntry() { 243 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 244 | Long linzKey = modelUtils.storeNetworkPlan(linz); 245 | 246 | NetworkPlanEntry schumpeter = createNetworkPlanEntry(linzKey, "Schumpeterstrasse", "123", 100, 200); 247 | Long schumpeterKey = modelUtils.storeNetworkPlanEntry(schumpeter); 248 | 249 | modelUtils.removeNetworkPlanEntry(schumpeterKey); 250 | 251 | Query query = pm.newQuery(NetworkPlanEntry.class, "key == keyParam"); 252 | query.declareParameters("Long keyParam"); 253 | Collection entries = (Collection) query.execute(schumpeterKey); 254 | assertNotNull(entries); 255 | assertEquals(0, entries.size()); 256 | } 257 | 258 | public void testRemoveNetworkPlanEntry2() { 259 | NetworkPlan linz = createNetworkPlan("linz", "linz", "http://oeffi.schildbach.de/plans/linz.png"); 260 | Long linzKey = modelUtils.storeNetworkPlan(linz); 261 | 262 | Long schumpeterKey = modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(linzKey, "Schumpeter", "1", 1, 1)); 263 | Long hbfKey = modelUtils.storeNetworkPlanEntry(createNetworkPlanEntry(linzKey, "Hauptbahnhof", "2", 2, 2)); 264 | 265 | modelUtils.removeNetworkPlanEntry(schumpeterKey); 266 | 267 | List entries = modelUtils.getNetworkPlanEntries(linzKey); 268 | assertNotNull(entries); 269 | assertEquals(1, entries.size()); 270 | 271 | NetworkPlanEntry hbfEntry = entries.get(0); 272 | assertEquals("Hauptbahnhof", hbfEntry.getName()); 273 | assertEquals(hbfKey, hbfEntry.getKey()); 274 | } 275 | 276 | private NetworkPlan createNetworkPlan(String networkId, String planId, String imageUrl) { 277 | NetworkPlan networkPlan = new NetworkPlan(); 278 | networkPlan.setNetworkId(networkId); 279 | networkPlan.setPlanId(planId); 280 | networkPlan.setImageUrl(imageUrl); 281 | return networkPlan; 282 | } 283 | 284 | private NetworkPlanEntry createNetworkPlanEntry(Long networkPlanId, String name, String stationId, int x, int y) { 285 | NetworkPlanEntry npe = new NetworkPlanEntry(); 286 | npe.setNetworkPlanKey(networkPlanId); 287 | npe.setName(name); 288 | npe.setStationId(stationId); 289 | npe.setX(x); 290 | npe.setY(y); 291 | return npe; 292 | } 293 | 294 | @Override 295 | protected void setUp() throws Exception { 296 | super.setUp(); 297 | helper.setUp(); 298 | pm = PMF.get().getPersistenceManager(); 299 | modelUtils = ModelUtils.getInstance(); 300 | } 301 | 302 | @Override 303 | protected void tearDown() throws Exception { 304 | super.tearDown(); 305 | helper.tearDown(); 306 | pm.close(); 307 | modelUtils = null; 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /src/test/resources/berlin_tram.csv: -------------------------------------------------------------------------------- 1 | # This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. 2 | # http://creativecommons.org/licenses/by-sa/3.0/ 3 | # 4 | # Author: Andreas Schildbach 5 | # 6 | # Pixel coordinates for Berlin BVG Tram Plan dated 2011-05-01, 7 | # rendered at a resolution of 2480 x 1754 8 | 9 | # M1 10 | bvg|9100540|Universitätsstr.|berlin_tram|757|929 11 | bvg|9100038|Am Kupfergraben|berlin_tram|779|929 12 | bvg|9100541|Georgenstr./Am Kupfergraben|berlin_tram|772|903 13 | bvg|9100001|S+U Friedrichstr. Bhf|berlin_tram|643|840 14 | bvg|9100019|U Oranienburger Tor|berlin_tram|662|802 15 | bvg|9100007|S Oranienburger Str.|berlin_tram|655|791 16 | bvg|9100512|Monbijouplatz|berlin_tram|728|791 17 | bvg|9100002|S Hackescher Markt|berlin_tram|795|817 18 | bvg|9100080|U Weinmeisterstr./Gipsstr.|berlin_tram|820|763 19 | bvg|9100023|U Rosenthaler Platz|berlin_tram|843|740 20 | bvg|9100042|Zionskirchplatz|berlin_tram|880|699 21 | bvg|9110505|Schwedter Str.|berlin_tram|907|671 22 | bvg|9110006|U Eberswalder Str.|berlin_tram|937|639 23 | bvg|9110503|Milastr.|berlin_tram|937|577 24 | bvg|9110001|S+U Schönhauser Allee|berlin_tram|940|546 25 | bvg|9110007|Schönhauser Allee/Bornholmer Str.|berlin_tram|946|473 26 | bvg|9130011|U Vinetastr.|berlin_tram|942|443 27 | bvg|9130512|Masurenstr.|berlin_tram|942|416 28 | bvg|9130002|S+U Pankow|berlin_tram|942|372 29 | bvg|9130013|Pankow Kirche|berlin_tram|946|339 30 | bvg|9130501|Rathaus Pankow|berlin_tram|920|325 31 | bvg|9131004|Bürgerpark Pankow|berlin_tram|899|317 32 | bvg|9131505|Tschaikowskistr.|berlin_tram|884|302 33 | bvg|9131528|Grabbeallee/Pastor-Niemöller-Platz|berlin_tram|869|288 34 | bvg|9131527|Hermann-Hesse-Str./Waldstr.|berlin_tram|879|270 35 | bvg|9131510|Kuckhoffstr.|berlin_tram|895|253 36 | bvg|9131526|Heinrich-Böll-Str.|berlin_tram|912|236 37 | bvg|9131006|Nordend|berlin_tram|930|219 38 | bvg|9131519|Waldemarstr.|berlin_tram|947|202 39 | bvg|9131001|Schillerstr.|berlin_tram|962|187 40 | bvg|9131005|Pastor-Niemöller-Platz|berlin_tram|850|267 41 | bvg|9131529|Am Iderfenngraben|berlin_tram|833|250 42 | bvg|9131507|Platanenstr.|berlin_tram|817|234 43 | bvg|9131508|Uhlandstr.|berlin_tram|800|217 44 | bvg|9132502|Nordendstr.|berlin_tram|783|200 45 | bvg|9132501|Angerweg|berlin_tram|765|183 46 | bvg|9132500|Wiesenwinkel|berlin_tram|748|166 47 | bvg|9132010|Hauptstr./Friedrich-Engels-Str.|berlin_tram|733|151 48 | bvg|9132014|Rosenthal Nord|berlin_tram|717|134 49 | # M2 50 | bvg|9100024|S+U Alexanderplatz/Dircksenstr.|berlin_tram|994|880 51 | bvg|9100030|Memhardstr.|berlin_tram|961|813 52 | bvg|9110022|Mollstr./Prenzlauer Allee|berlin_tram|1009|765 53 | bvg|9110506|Prenzlauer Allee/Metzer Str.|berlin_tram|1057|718 54 | bvg|9110507|Knaackstr.|berlin_tram|1078|697 55 | bvg|9110508|Marienburger Str.|berlin_tram|1101|674 56 | bvg|9110017|Prenzlauer Allee/Danziger Str.|berlin_tram|1136|639 57 | bvg|9110510|Fröbelstr.|berlin_tram|1181|582 58 | bvg|9110002|S Prenzlauer Allee|berlin_tram|1181|545 59 | bvg|9110509|Erich-Weinert-Str.|berlin_tram|1181|507 60 | bvg|9110013|Prenzlauer Allee/Ostseestr.|berlin_tram|1181|473 61 | bvg|9141507|Prenzlauer Promenade/Am Steinberg|berlin_tram|1181|428 62 | bvg|9141506|Am Steinberg|berlin_tram|1188|388 63 | bvg|9141502|Berliner Str./Wiesenstr.|berlin_tram|1204|371 64 | bvg|9141504|Am Wasserturm|berlin_tram|1221|354 65 | bvg|9141002|Heinersdorf Kirche|berlin_tram|1237|337 66 | bvg|9141503|Rothenbachstr.|berlin_tram|1253|321 67 | bvg|9141001|Heinersdorf|berlin_tram|1271|305 68 | # M4 69 | bvg|9100515|Spandauer Str./Marienkirche|berlin_tram|908|865 70 | bvg|9100026|S+U Alexanderplatz Bhf/Gontardstr.|berlin_tram|963|913 71 | bvg|9100005|U Alexanderplatz|berlin_tram|1029|892 72 | bvg|9100040|Mollstr./Otto-Braun-Str.|berlin_tram|1089|831 73 | bvg|9110019|Am Friedrichshain|berlin_tram|1151|781 74 | bvg|9110521|Hufelandstr.|berlin_tram|1184|750 75 | bvg|9110020|Greifswalder Str./Danziger Str.|berlin_tram|1225|707 76 | bvg|9110003|S Greifswalder Str.|berlin_tram|1289|644 77 | bvg|9110515|Thomas-Mann-Str.|berlin_tram|1325|608 78 | bvg|9110016|Greifswalder Str./Ostseestr.|berlin_tram|1350|584 79 | bvg|9140011|Antonplatz|berlin_tram|1376|556 80 | bvg|9140005|Albertinenstr.|berlin_tram|1414|556 81 | bvg|9140006|Berliner Allee/Indira-Gandhi-Str.|berlin_tram|1450|556 82 | bvg|9140519|Buschallee|berlin_tram|1502|528 83 | bvg|9140518|Sulzfelder Str.|berlin_tram|1528|528 84 | bvg|9140007|Buschallee/Hansastr.|berlin_tram|1569|526 85 | bvg|9140014|Giersstr.|berlin_tram|1637|461 86 | bvg|9140517|Stadion Buschallee/Hansastr.|berlin_tram|1656|438 87 | bvg|9150501|Feldtmannstr.|berlin_tram|1676|420 88 | bvg|9150002|Hansastr./Malchower Weg|berlin_tram|1696|399 89 | bvg|9151006|Prerower Platz|berlin_tram|1726|375 90 | bvg|9152001|S Hohenschönhausen Bhf|berlin_tram|1748|353 91 | bvg|9152006|Falkenberger Ch./Prendener Str.|berlin_tram|1768|334 92 | bvg|9152003|Welsestr.|berlin_tram|1782|319 93 | bvg|9152007|Falkenberg|berlin_tram|1798|306 94 | bvg|9151501|Ahrenshooper Str.|berlin_tram|1693|344 95 | bvg|9151004|Zingster Str./Ribnitzer Str.|berlin_tram|1672|324 96 | bvg|9151003|Zingster Str.|berlin_tram|1646|298 97 | # M5 98 | bvg|9120511|Büschingstr.|berlin_tram|1144|829 99 | bvg|9120019|Platz der Vereinten Nationen|berlin_tram|1171|829 100 | bvg|9120513|Klinikum im Friedrichshain|berlin_tram|1226|829 101 | bvg|9120016|Landsberger Allee/Petersburger Str.|berlin_tram|1275|829 102 | bvg|9110004|S Landsberger Allee|berlin_tram|1402|829 103 | bvg|9110030|Oderbruchstr.|berlin_tram|1497|752 104 | bvg|9160508|Judith-Auer-Str.|berlin_tram|1518|731 105 | bvg|9150011|Hohenschönhauser Str./Weißenseer Weg|berlin_tram|1559|694 106 | bvg|9150510|Sandinostr.|berlin_tram|1620|623 107 | bvg|9150511|Simon-Bolivar-Str.|berlin_tram|1634|608 108 | bvg|9150512|Werneuchener Str.|berlin_tram|1649|594 109 | bvg|9150513|Freienwalder Str.|berlin_tram|1664|579 110 | bvg|9150007|Oberseestr.|berlin_tram|1680|563 111 | bvg|9150020|Hauptstr./Rhinstr.|berlin_tram|1739|530 112 | bvg|9150504|Gehrenseestr.|berlin_tram|1780|491 113 | bvg|9150500|Anna-Ebermann-Str.|berlin_tram|1780|451 114 | bvg|9152509|Arnimstr.|berlin_tram|1771|419 115 | bvg|9152508|Rüdickenstr.|berlin_tram|1750|399 116 | # M6 117 | bvg|9100501|U Schwartzkopffstr.|berlin_tram|521|711 118 | bvg|9100034|Pflugstr.|berlin_tram|517|660 119 | bvg|9100536|Habersaathstr.|berlin_tram|539|732 120 | bvg|9100009|U Naturkundemuseum|berlin_tram|558|751 121 | bvg|9100506|Torstr./U Oranienburger Tor|berlin_tram|585|783 122 | bvg|9150509|Altenhofer Str.|berlin_tram|1600|683 123 | bvg|9160513|Zechliner Str.|berlin_tram|1626|683 124 | bvg|9160514|Genslerstr.|berlin_tram|1652|683 125 | bvg|9160515|Arendsweg|berlin_tram|1679|683 126 | bvg|9160516|Schalkauer Str.|berlin_tram|1705|683 127 | bvg|9171011|Landsberger Allee/Rhinstr.|berlin_tram|1779|686 128 | bvg|9170520|Dingelstädter Str.|berlin_tram|1807|683 129 | bvg|9170521|Gewerbepark Georg Knorr|berlin_tram|1845|683 130 | bvg|9170001|S Marzahn|berlin_tram|1929|670 131 | bvg|9170020|Marzahner Promenade|berlin_tram|1970|634 132 | bvg|9170519|Freizeitforum Marzahn|berlin_tram|2003|601 133 | bvg|9170517|Jan-Petersen-Str.|berlin_tram|2042|596 134 | bvg|9170013|Landsberger Allee/Blumberger Damm|berlin_tram|2079|607 135 | bvg|9170524|Brodowiner Ring|berlin_tram|2100|618 136 | bvg|9170010|Betriebshof Marzahn|berlin_tram|2117|636 137 | bvg|9175510|Landsberger Chaussee/Zossener Str.|berlin_tram|2133|652 138 | bvg|9175511|Michendorfer Str.|berlin_tram|2150|668 139 | bvg|9175512|Alte Hellersdorfer/Zossener Str.|berlin_tram|2165|684 140 | bvg|9175513|Zossener Str./Kastanienallee|berlin_tram|2181|700 141 | bvg|9175514|Stendaler Str./Zossener Str.|berlin_tram|2197|716 142 | bvg|9175011|Stendaler Str./Quedlinburger Str.|berlin_tram|2212|731 143 | bvg|9175007|U Hellersdorf|berlin_tram|2226|748 144 | bvg|9175519|Nossener Str.|berlin_tram|2250|756 145 | bvg|9175520|Jenaer Str.|berlin_tram|2287|756 146 | bvg|9175014|Riesaer Str./Louis-Lewin-Str.|berlin_tram|2319|756 147 | bvg|9175013|Riesaer Str.|berlin_tram|2350|756 148 | # M8 149 | bvg|9007104|S Nordbahnhof|berlin_tram|656|701 150 | bvg|9100504|Pappelplatz|berlin_tram|734|702 151 | bvg|9100041|Brunnenstr./Invalidenstr.|berlin_tram|788|704 152 | bvg|9100016|U Rosa-Luxemburg-Platz|berlin_tram|936|766 153 | bvg|9110029|Landsberger Allee/Karl-Lade-Str.|berlin_tram|1453|838 154 | bvg|9160510|Anton-Saefkow-Platz|berlin_tram|1477|838 155 | bvg|9160022|Paul-Junius-Str.|berlin_tram|1539|838 156 | bvg|9160509|Bernhard-Bästlein-Str.|berlin_tram|1589|838 157 | bvg|9160518|Herzbergstr./Industriegebiet|berlin_tram|1623|838 158 | bvg|9160012|Herzbergstr./Siegfriedstr.|berlin_tram|1649|841 159 | bvg|9160520|Ev. Krankenhaus KEH|berlin_tram|1720|841 160 | bvg|9171008|Allee der Kosmonauten/Rhinstr.|berlin_tram|1780|844 161 | bvg|9171515|Beilsteiner Str.|berlin_tram|1817|838 162 | bvg|9171003|S Springpfuhl|berlin_tram|1855|841 163 | bvg|9171516|Helene-Weigel-Platz|berlin_tram|1900|817 164 | bvg|9171517|Boschpoler Str.|berlin_tram|1934|783 165 | bvg|9170019|Allee der Kosmonauten/Poelchaustr.|berlin_tram|1978|740 166 | bvg|9170515|Adersleber Weg|berlin_tram|2009|709 167 | bvg|9170516|Alt-Marzahn|berlin_tram|2042|676 168 | bvg|9170529|Bürgerpark Marzahn|berlin_tram|2062|545 169 | bvg|9170530|Max-Herrmann-Str.|berlin_tram|2083|524 170 | bvg|9170531|Wuhletalstr.|berlin_tram|2103|504 171 | bvg|9170506|Niemegker Str.|berlin_tram|2125|482 172 | bvg|9170011|Barnimplatz|berlin_tram|2150|457 173 | bvg|9170006|Ahrensfelde|berlin_tram|2174|432 174 | # M10 175 | bvg|9007150|Gedenkstätte Berliner Mauer|berlin_tram|706|663 176 | bvg|9007110|U Bernauer Str.|berlin_tram|747|639 177 | bvg|9007105|Wolliner Str.|berlin_tram|804|639 178 | bvg|9110018|Friedrich-Ludwig-Jahn-Sportpark|berlin_tram|890|639 179 | bvg|9110511|Husemannstr.|berlin_tram|1076|639 180 | bvg|9110512|Winsstr.|berlin_tram|1196|678 181 | bvg|9110025|Arnswalder Platz|berlin_tram|1246|728 182 | bvg|9110021|Kniprodestr./Danziger Str.|berlin_tram|1269|750 183 | bvg|9110523|Paul-Heyse-Str.|berlin_tram|1275|777 184 | bvg|9120515|Straßmannstr.|berlin_tram|1275|888 185 | bvg|9120516|Bersarinplatz|berlin_tram|1278|953 186 | bvg|9120008|U Frankfurter Tor|berlin_tram|1278|997 187 | bvg|9120014|Grünberger Str./Warschauer Str.|berlin_tram|1257|1030 188 | bvg|9120021|Revaler Str.|berlin_tram|1215|1076 189 | bvg|9120011|S Warschauer Str.|berlin_tram|1206|1086 190 | bvg|9120004|S+U Warschauer Str.|berlin_tram|1185|1102 191 | # M13 192 | bvg|9120526|Kopernikusstr./Warschauer Str.|berlin_tram|1244|1080 193 | bvg|9120527|Libauer Str.|berlin_tram|1278|1080 194 | bvg|9120528|Simplonstr.|berlin_tram|1298|1080 195 | bvg|9120529|Wühlischstr./Gärtnerstr.|berlin_tram|1320|1080 196 | bvg|9120013|Boxhagener Str./Holteistr.|berlin_tram|1344|1080 197 | bvg|9120540|Scharnweberstr.|berlin_tram|1376|1068 198 | bvg|9120001|S+U Frankfurter Allee|berlin_tram|1408|1026 199 | bvg|9160544|Rathaus Lichtenberg|berlin_tram|1440|1002 200 | bvg|9160017|Loeperplatz|berlin_tram|1519|929 201 | bvg|9160014|Möllendorffstr./Storkower Str.|berlin_tram|1550|899 202 | bvg|9160010|Herzbergstr./Weißenseer Weg|berlin_tram|1556|822 203 | bvg|9160018|Landsberger Allee/Weißenseer Weg|berlin_tram|1556|736 204 | bvg|9150519|Sportforum|berlin_tram|1558|663 205 | bvg|9150518|Betriebshof Indira-Gandhi-Str.|berlin_tram|1536|625 206 | bvg|9150522|Gounodstr.|berlin_tram|1514|603 207 | bvg|9140506|Behaimstr.|berlin_tram|1350|534 208 | bvg|9140505|Friesickestr.|berlin_tram|1328|512 209 | bvg|9140017|Gustav-Adolf-Str./Langhansstr.|berlin_tram|1304|488 210 | bvg|9110015|Stahlheimer Str./Wisbyer Str.|berlin_tram|1116|474 211 | bvg|9110008|Schönfließer Str.|berlin_tram|853|471 212 | bvg|9110010|Björnsonstr.|berlin_tram|808|471 213 | bvg|9110011|S Bornholmer Str.|berlin_tram|726|472 214 | bvg|9007160|Grüntaler Str.|berlin_tram|634|443 215 | bvg|9009204|Osloer Str./Prinzenallee|berlin_tram|581|392 216 | bvg|9009206|Drontheimer Str.|berlin_tram|522|358 217 | bvg|9009202|U Osloer Str.|berlin_tram|466|358 218 | bvg|9011201|Louise-Schroeder-Platz|berlin_tram|331|372 219 | bvg|9011252|Osram-Höfe|berlin_tram|303|400 220 | bvg|9009103|U Seestr.|berlin_tram|260|446 221 | bvg|9009105|Seestr./Amrumer Str.|berlin_tram|228|474 222 | bvg|9010251|Virchow-Klinikum|berlin_tram|203|500 223 | # M17 224 | bvg|9194006|S Schöneweide Bhf/Sterndamm|berlin_tram|1552|1500 225 | bvg|9192001|S Schöneweide Bhf|berlin_tram|1570|1480 226 | bvg|9192506|Brückenstr.|berlin_tram|1624|1428 227 | bvg|9181001|Wilhelminenhofstr./Edisonstr.|berlin_tram|1676|1378 228 | bvg|9181002|Rummelsburger Str./Edisonstr.|berlin_tram|1715|1333 229 | bvg|9181501|Treskowallee/Volkspark Wuhlheide|berlin_tram|1747|1302 230 | bvg|9162002|Hegemeisterweg|berlin_tram|1768|1280 231 | bvg|9162003|Treskowallee/Ehrlichstr.|berlin_tram|1772|1231 232 | bvg|9162501|S Karlshorst Bhf/Wandlitzstr.|berlin_tram|1779|1204 233 | bvg|9162001|S Karlshorst Bhf|berlin_tram|1779|1181 234 | bvg|9162007|Marksburgstr.|berlin_tram|1779|1152 235 | bvg|9162004|Treskowallee/HTW|berlin_tram|1779|1118 236 | bvg|9161002|U Tierpark|berlin_tram|1779|1076 237 | bvg|9161508|Criegernweg|berlin_tram|1779|1036 238 | bvg|9161507|Tierpark Berlin|berlin_tram|1779|997 239 | bvg|9161004|Alt-Friedrichsfelde/Rhinstr.|berlin_tram|1779|960 240 | bvg|9171002|S Friedrichsfelde Ost|berlin_tram|1779|913 241 | bvg|9171540|Kleingartenanlage Bielefeldt|berlin_tram|1779|888 242 | bvg|9171008|Allee der Kosmonauten/Rhinstr.|berlin_tram|1779|844 243 | bvg|9171514|Meeraner Str.|berlin_tram|1779|747 244 | bvg|9150005|Rhinstr./Plauener Str.|berlin_tram|1779|618 245 | bvg|9150008|Rhinstr./Gärtnerstr.|berlin_tram|1779|580 246 | # 12 247 | bvg|9110009|U Eberswalder Str./Pappelallee|berlin_tram|962|611 248 | bvg|9110502|Raumerstr.|berlin_tram|986|587 249 | bvg|9110501|Stargarder Str.|berlin_tram|1006|567 250 | bvg|9110500|Humannplatz|berlin_tram|1064|509 251 | bvg|9140012|Falkenberger Str./Berliner Allee|berlin_tram|1482|497 252 | bvg|9140013|Berliner Allee/Rennbahnstr.|berlin_tram|1461|456 253 | bvg|9140002|Pasedagplatz|berlin_tram|1430|426 254 | # 16 255 | # 18 256 | bvg|9160526|Fanningerstr.|berlin_tram|1660|963 257 | bvg|9160525|Guntherstr.|berlin_tram|1670|980 258 | bvg|9160021|S+U Lichtenberg Bhf/Gudrunstr.|berlin_tram|1674|992 259 | bvg|9160020|S+U Lichtenberg Bhf/Siegfriedstr.|berlin_tram|1660|992 260 | bvg|9160016|Freiaplatz|berlin_tram|1660|948 261 | bvg|9160523|Gotlindestr.|berlin_tram|1660|930 262 | bvg|9160522|Betriebshof Lichtenberg|berlin_tram|1663|905 263 | bvg|9160521|Siegfriedstr./Josef-Orlopp-Str.|berlin_tram|1663|876 264 | # 21 265 | bvg|9160538|Scheffelstr./Paul-Junius-Str.|berlin_tram|1449|942 266 | bvg|9120539|Samariterstr.|berlin_tram|1368|942 267 | bvg|9120538|Proskauer Str.|berlin_tram|1343|942 268 | bvg|9120542|Forckenbeckplatz|berlin_tram|1323|942 269 | bvg|9120517|Niederbarnimstr.|berlin_tram|1302|1038 270 | bvg|9120518|Wismarplatz|berlin_tram|1320|1056 271 | bvg|9120519|Neue Bahnhofstr.|berlin_tram|1379|1115 272 | bvg|9160535|Marktstr.|berlin_tram|1467|1126 273 | bvg|9160001|S Rummelsburg|berlin_tram|1558|1175 274 | bvg|9160536|Kosanke-Siedlung|berlin_tram|1566|1190 275 | bvg|9160502|Gustav-Holzmann-Str.|berlin_tram|1580|1205 276 | bvg|9160006|Heizkraftwerk|berlin_tram|1595|1219 277 | bvg|9160007|Köpenicker Chaussee/Blockdammweg|berlin_tram|1610|1233 278 | bvg|9162504|Blockdammweg/Ehrlichstr.|berlin_tram|1653|1238 279 | bvg|9162503|Stechlinstr.|berlin_tram|1694|1238 280 | bvg|9162502|Stühlinger Str.|berlin_tram|1731|1238 281 | # 27 282 | bvg|9150515|Stadion Buschallee/Suermondtstr.|berlin_tram|1623|526 283 | bvg|9150514|Am Faulen See|berlin_tram|1657|526 284 | bvg|9150010|Degnerstr./Suermondtstr.|berlin_tram|1706|526 285 | bvg|9181502|Firlstr.|berlin_tram|1700|1387 286 | bvg|9181503|Rathenaustr./HTW|berlin_tram|1722|1387 287 | bvg|9181504|Ostendstr.|berlin_tram|1743|1387 288 | bvg|9181505|Parkstr.|berlin_tram|1763|1387 289 | bvg|9181003|Freizeit- und Erholungszentrum|berlin_tram|1790|1387 290 | bvg|9181506|Nixenstr.|berlin_tram|1820|1387 291 | bvg|9181507|Spindlersfelder Str.|berlin_tram|1842|1387 292 | bvg|9181513|Alte Försterei|berlin_tram|1862|1387 293 | bvg|9180008|Bahnhofstr./Lindenstr.|berlin_tram|1884|1383 294 | bvg|9180024|Rathaus Köpenick|berlin_tram|1962|1412 295 | bvg|9180007|Schloßplatz Köpenick|berlin_tram|1992|1434 296 | bvg|9180005|Müggelheimer Str./Wendenschloßstr.|berlin_tram|2068|1440 297 | bvg|9180004|Pablo-Neruda-Str.|berlin_tram|2095|1440 298 | bvg|9180022|Krankenhaus Köpenick/Südseite|berlin_tram|2123|1440 299 | # 50 300 | bvg|9130502|Stiftsweg|berlin_tram|973|324 301 | bvg|9130520|Mendelstr.|berlin_tram|1002|324 302 | bvg|9130522|Würtzstr.|berlin_tram|1030|320 303 | bvg|9130001|S Pankow-Heinersdorf|berlin_tram|1058|305 304 | bvg|9130016|Galenusstr.|berlin_tram|1065|280 305 | bvg|9130017|Pankower Str.|berlin_tram|1065|260 306 | bvg|9134503|Pasewalker Str./Blankenburger Weg|berlin_tram|1065|238 307 | bvg|9134502|Marienstr./Pasewalker Str.|berlin_tram|1065|199 308 | bvg|9134501|Rosenthaler Str.|berlin_tram|1065|182 309 | bvg|9134500|Blankenfelder Str.|berlin_tram|1058|169 310 | bvg|9134011|Französisch Buchholz Kirche|berlin_tram|1044|156 311 | bvg|9134512|Navarraplatz|berlin_tram|1031|142 312 | bvg|9134513|Arnouxstr.|berlin_tram|1005|133 313 | bvg|9134509|Hugenottenplatz|berlin_tram|983|133 314 | bvg|9134511|Guyotstr.|berlin_tram|963|133 315 | # 60 316 | bvg|9193002|S Adlershof|berlin_tram|1686|1596 317 | bvg|9193505|Marktplatz Adlershof|berlin_tram|1708|1574 318 | bvg|9193506|Wassermannstr.|berlin_tram|1727|1555 319 | bvg|9180510|Ottomar-Geschke-Str.|berlin_tram|1748|1533 320 | bvg|9180003|S Spindlersfeld|berlin_tram|1781|1502 321 | bvg|9180012|Köllnischer Platz|berlin_tram|1909|1426 322 | bvg|9180006|Freiheit|berlin_tram|2006|1399 323 | bvg|9180009|Bahnhofstr./Seelenbinderstr.|berlin_tram|1883|1302 324 | bvg|9180511|Gelnitzstr.|berlin_tram|1930|1302 325 | bvg|9180512|Brandenburgplatz|berlin_tram|1957|1302 326 | bvg|9180013|Bellevuestr.|berlin_tram|1985|1302 327 | bvg|9182003|Hirschgarten|berlin_tram|2010|1302 328 | bvg|9182501|Westendsiedlung|berlin_tram|2112|1294 329 | bvg|9182502|Mühlweg|berlin_tram|2125|1282 330 | bvg|9182503|Ahornallee|berlin_tram|2138|1269 331 | bvg|9182002|S Friedrichshagen|berlin_tram|2153|1255 332 | bvg|9182504|Drachholzstr.|berlin_tram|2153|1283 333 | bvg|9182505|Marktplatz Friedrichshagen|berlin_tram|2153|1304 334 | bvg|9182004|Müggelseedamm/Bölschestr.|berlin_tram|2153|1324 335 | bvg|9182506|Josef-Nawrocki-Str.|berlin_tram|2177|1339 336 | bvg|9182507|Bruno-Wille-Str.|berlin_tram|2228|1339 337 | bvg|9182005|Altes Wasserwerk|berlin_tram|2259|1339 338 | # 61 339 | bvg|9182508|Wassersportzentrum|berlin_tram|2118|1316 340 | bvg|9182509|Spreestr.|berlin_tram|2136|1334 341 | bvg|9182510|Hartlebenstr.|berlin_tram|2184|1264 342 | bvg|9182511|Wasserwerk Friedrichshagen|berlin_tram|2207|1264 343 | bvg|9182512|Fürstenwalder Damm|berlin_tram|2230|1264 344 | bvg|9182513|Fürstenwalder Damm/Müggelseedamm|berlin_tram|2259|1264 345 | bvg|9183513|Licht- und Luftbad Müggelsee|berlin_tram|2298|1264 346 | bvg|9183514|Strandbad Müggelsee|berlin_tram|2326|1264 347 | bvg|9183003|Rahnsdorf/Waldschänke|berlin_tram|2345|1264 348 | # 62 349 | bvg|9180015|Wendenschloß|berlin_tram|2069|1640 350 | bvg|9180014|Müggelbergallee|berlin_tram|2069|1622 351 | bvg|9180520|Lienhardweg|berlin_tram|2069|1605 352 | bvg|9180519|Zur Nachtheide|berlin_tram|2069|1588 353 | bvg|9180518|Dregerhoffstr.|berlin_tram|2069|1571 354 | bvg|9180517|Pritstabelstr.|berlin_tram|2069|1554 355 | bvg|9180516|Segewaldweg|berlin_tram|2069|1539 356 | bvg|9180515|Mayschweg|berlin_tram|2069|1523 357 | bvg|9180514|Betriebshof Köpenick|berlin_tram|2069|1507 358 | bvg|9180010|Mahlsdorfer Str./Gehsener Str.|berlin_tram|1999|1156 359 | bvg|9180526|Wongrowitzer Steig|berlin_tram|2017|1137 360 | bvg|9180525|Unter den Birken|berlin_tram|2035|1119 361 | bvg|9180017|Mahlsdorf-Süd|berlin_tram|2053|1102 362 | bvg|9176512|Hultschiner Damm/Seestr.|berlin_tram|2071|1084 363 | bvg|9176533|Roseggerstr.|berlin_tram|2089|1065 364 | bvg|9176534|Erich-Baron-Weg|berlin_tram|2107|1047 365 | bvg|9176535|Bütower Str.|berlin_tram|2125|1030 366 | bvg|9176536|Bruchsaler Str.|berlin_tram|2143|1012 367 | bvg|9176537|Eichenhofweg|berlin_tram|2161|994 368 | bvg|9176526|Ledebourstr.|berlin_tram|2180|976 369 | bvg|9176525|Rahnsdorfer Str.|berlin_tram|2197|958 370 | bvg|9176524|Alt-Mahlsdorf|berlin_tram|2214|941 371 | bvg|9176008|Wilhelmsmühlenweg|berlin_tram|2231|923 372 | bvg|9176001|S Mahlsdorf|berlin_tram|2253|901 373 | # 63 374 | bvg|9194506|Johannisthal Kirche|berlin_tram|1504|1553 375 | bvg|9194521|Mühlbergstr.|berlin_tram|1502|1571 376 | bvg|9194012|Haeckelstr.|berlin_tram|1515|1579 377 | bvg|9194520|Herweghstr.|berlin_tram|1528|1555 378 | bvg|9194007|Sterndamm/Königsheideweg|berlin_tram|1521|1536 379 | bvg|9194522|Pietschkerstr.|berlin_tram|1538|1520 380 | bvg|9180016|Hirtestr.|berlin_tram|1992|1204 381 | bvg|9180513|Hirtestr./Janitzkystr.|berlin_tram|2012|1183 382 | # 67 383 | # 68 384 | bvg|9180524|Glienicker Str.|berlin_tram|1889|1501 385 | bvg|9180523|Vollkropfgraben|berlin_tram|1868|1522 386 | bvg|9180522|Rosenweg|berlin_tram|1849|1541 387 | bvg|9180521|Betonwerk|berlin_tram|1829|1560 388 | bvg|9186506|Friedrich-Wolf-Str.|berlin_tram|1811|1579 389 | bvg|9186505|Regattastr./Schule|berlin_tram|1792|1598 390 | bvg|9186002|Wassersportallee|berlin_tram|1774|1616 391 | bvg|9186001|S Grünau|berlin_tram|1745|1655 392 | bvg|9186504|Regattatribünen|berlin_tram|1782|1658 393 | bvg|9186503|Regattastr./Sportpromenade|berlin_tram|1814|1658 394 | bvg|9186502|Strandbad Grünau|berlin_tram|1847|1658 395 | bvg|9186501|Richtershorn|berlin_tram|1873|1658 396 | bvg|9185505|Schappachstr.|berlin_tram|1899|1658 397 | bvg|9185504|Lübbenauer Weg|berlin_tram|1926|1658 398 | bvg|9185503|Adlergestell/Vetschauer Allee|berlin_tram|1966|1675 399 | bvg|9185502|Reifenwerk|berlin_tram|1985|1695 400 | bvg|9185501|Zum Seeblick|berlin_tram|2003|1713 401 | bvg|9185001|Alt-Schmöckwitz|berlin_tram|2021|1731 402 | 403 | --------------------------------------------------------------------------------