Skip to content

Commit

Permalink
storypins edit and playback work, refs #135
Browse files Browse the repository at this point in the history
- local storage service for persistence
- add/edit/delete working
- playback and timeline
- many tests added
- start/end date in filtering, refs #136
- listen for layer changes and compute time, refs #134
  • Loading branch information
ischneider committed Feb 10, 2015
1 parent d53ed47 commit 0ad37ef
Show file tree
Hide file tree
Showing 26 changed files with 937 additions and 235 deletions.
7 changes: 7 additions & 0 deletions examples/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@
var url = layer.get('server').timeEndpoint(layer);
$http.get(url).success(function(data) {
layer.set('timeAttribute', data.attribute);
if (data.endAttribute) {
// @todo verify this with sample from mapstory that has endAttribute
layer.set('endTimeAttribute', data.endAttribute);
}
});
} else {
// @todo make sure we have time attribute _somehow_
Expand Down Expand Up @@ -387,6 +391,9 @@
scope.map.map.getLayers().on('change:length', function() {
scope.layers = scope.map.getNamedLayers();
});
scope.removeLayer = function(lyr) {
scope.map.map.removeLayer(lyr);
};
}
};
});
Expand Down
54 changes: 35 additions & 19 deletions examples/full-example.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,20 @@
#middle{
display: table-cell;
}
.side {
display: table-cell;
vertical-align: middle;
float: none;
}
.floating {
left: 0;
position: absolute;
z-index: 500;
padding: 1em;
}
.floating-below {
z-index: 400;
}
#map {
width: 100%;
height: 100%;
Expand All @@ -78,10 +86,17 @@
top: 4.5em;
bottom: auto;
}
#time-controls-panel, #time-controls-panel > div {
#time-controls-panel {
padding: 0;
width: 100%;
bottom: 0;
width: 100%;
}
#time-controls-panel > div {
padding: 1px 16px;
background:white;
}
#time-controls-panel2 {
padding: 1px 16px;
}
.todo {
color: white;
Expand Down Expand Up @@ -145,39 +160,40 @@
</div>
</div>
<!-- @todo style-editor-item class used for convenience -->
<div ng-if="selected.pins" class="floating col-lg-4 style-editor-item" pins-editor>
<accordion close-others="true" ng-init="open={1:true,2:false}">
<accordion-group heading="Add StoryPin" is-open="open.1">
<div ng-if="selected.pins" class="floating col-lg-6 style-editor-item" pins-editor pins-layer-manager="pinsLayerManager" pins-overlay="pinsOverlay">
<accordion close-others="false" ng-init="open={chooser:true,editor:false}">
<accordion-group heading="StoryPins" is-open="open.chooser">
<button>Bulk Upload</button>
<button ng-click="pinsCtrl.editStoryPin(); open.editor=true">Add StoryPin</button>
<pin-chooser pin-selected="open.editor=true"></pin-chooser>
</accordion-group>
<accordion-group is-open="open.editor">
<accordion-heading><div ng-bind="pinsCtrl.currentPin ? 'Edit StoryPin ' + pinsCtrl.currentPin.title : 'Add StoryPin'"></div></accordion-heading>
<div pin-editor>
<pin-editor-form></pin-editor-form>
<button ng-disabled="!pinCtrl.isFormValid()" ng-click="pinsCtrl.addStoryPin(pinCtrl.finish())">Add Pin</button>
<span ng-show="storyPinAdded" class="ng-hide">Added</span>
<button ng-disabled="!pinCtrl.isFormValid()" ng-click="pinsCtrl.saveStoryPin()">Save</button>
</div>
</accordion-group>
<accordion-group heading="StoryPins" is-open="open.2">
<button>Bulk Upload</button>
<pin-chooser story-pins="storyPins"></pin-chooser>
</accordion-group>
</accordion>
</div>
<div id="time-controls-panel" ng-show="selected.preview" class="floating ng-hide">
<div style="position:absolute; bottom:0; background:white">
<div id="slider"></div>
<st-playback-controls ng-show="timeControls" time-controls="timeControls"></st-playback-controls>
<div id="timeline"></div>
</div>
</div>
<!--<div id="time-controls-panel" ng-show="true||selected.preview||selected.pins" class="floating floating-below ng-hide">
</div>-->
<div ng-show="selected.preview" class="floating ng-hide col-lg-4">
<div class="panel">
<div class="panel-heading">Playback Options</div>
<div class="panel-body">
<st-playback-settings playback-options="playbackOptions" time-controls="timeControls"></st-playback-settings>
<st-playback-settings playback-options="playbackOptions" time-controls="timeControlsManager.timeControls"></st-playback-settings>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="time-controls-panel2">
<div id="slider"></div>
<st-playback-controls ng-show="timeControlsManager.timeControls" time-controls="timeControlsManager.timeControls"></st-playback-controls>
<div id="timeline"></div>
</div>
</div>
</div>
</body>
Expand Down
56 changes: 21 additions & 35 deletions examples/full-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,37 @@
'ui.bootstrap'
]);

module.controller('exampleController', function($scope, mapFactory, stTimeControlsFactory, styleUpdater) {
// @todo determine mapid approach - this is currently used by stAnnotationsStore
// longer term, this could be controlled using a route, for now it's Jenny
module.constant('mapid', 8675309);

module.controller('exampleController', function($scope, mapFactory, TimeControlsManager,
styleUpdater, stAnnotationsStore, StoryPinLayerManager) {
$scope.map = mapFactory.create();
$scope.pinsOverlay = new ol.FeatureOverlay({
map: $scope.map.map
});

var storyPins = stAnnotationsStore.loadAnnotations();
var pinsLayerManager = new StoryPinLayerManager(storyPins);
var timeControlsManager = new TimeControlsManager({
mode: $scope.map.mode,
map: $scope.map.map,
pinsLayerManager: pinsLayerManager
});
$scope.pinsLayerManager = pinsLayerManager;
$scope.timeControlsManager = timeControlsManager;
$scope.map.map.addLayer(pinsLayerManager.storyPinsLayer);

$scope.timeControls = null;
$scope.playbackOptions = {
mode: 'instant',
fixed: false
};

// we currently need to lazily create the timeControls, should probably extract this
// @todo! we don't want to create the timeControls here if there is a saved configuration
$scope.map.map.getLayers().on('change:length', function() {
if ($scope.timeControls == null) {
// need some layer with time to start with
var hasTime = false;
$scope.map.map.getLayers().forEach(function(l) {
hasTime |= angular.isDefined(l.get('times'));
});
if (hasTime) {
$scope.timeControls = stTimeControlsFactory.create({mode: $scope.map.mode, map: $scope.map.map});
if ($scope.map.mode) {
$scope.playbackOptions.mode = $scope.map.mode;
}
}
}
});
$scope.styleChanged = function(layer) {
styleUpdater.updateStyle(layer);
};

$scope.overlay = new ol.FeatureOverlay({
map: $scope.map.map
});

var defaultStyle = [new ol.style.Style({
fill: new ol.style.Fill({color: 'rgba(255, 0, 0, 0.1)'}),
stroke: new ol.style.Stroke({color: 'red', width: 1}),
image: new ol.style.Circle({
radius: 10,
fill: new ol.style.Fill({color: 'rgba(255, 0, 0, 0.1)'}),
stroke: new ol.style.Stroke({color: 'red', width: 1})
})
})];
var pinsLayer = new ol.layer.Vector({source: new ol.source.Vector(), style: defaultStyle});
$scope.pinsLayer = pinsLayer;
$scope.map.map.addLayer(pinsLayer);

});
})();
1 change: 1 addition & 0 deletions examples/templates/layer-list.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<div ng-repeat="lyr in layers">
{{lyr.get('title')}}
<button class="pull-right btn btn-small" ng-click="removeLayer(lyr)"><i class="glyphicon glyphicon-remove-circle"></i></button>
</div>
2 changes: 2 additions & 0 deletions lib/core/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
exports.time = require('./time/controls');
exports.maps = require('./time/maps');
exports.utils = require('./time/utils');
10 changes: 10 additions & 0 deletions lib/core/time/boxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ exports.BoxModel = function(boxArray) {
}
return idx;
};
this.setRange = function(newRange) {
if (boxes.length == 1) {
boxes[0].range = newRange;
range = newRange;
} else {
// @todo finish
console.log('more than one story box to update range with!');
}
updateBoxes(boxArray);
};
this.update = function(options) {

};
Expand Down
16 changes: 7 additions & 9 deletions lib/core/time/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,20 +259,17 @@ function PlaybackControls(id) {
* appearance
*/
function Annotations(annotations) {
var mapAnnotations;
function inTimeline(yes) {
return $.map(annotations, function(ann) {
return yes === ann.in_timeline ? ann : null;
var ann = annotations || [];
function inTimeline() {
return ann.filter(function(a) {
return a.in_timeline;
});
}
mapAnnotations = inTimeline(false);
this.getTimeLineAnnotatons = function() {
return inTimeline(true);
};
this.getMapAnnotations = function(instantOrRange) {
return $map(mapAnnotations, function(ann) {
// @todo
});
this.update = function(annotations) {
this.ann = annotations;
};
}

Expand Down Expand Up @@ -364,3 +361,4 @@ function create(options) {

exports.create = create;
exports.maps = maps;
exports.utils = utils;
6 changes: 3 additions & 3 deletions lib/core/time/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ exports.TimeLine = function(id, model) {
function init(model) {
var elements = [], options;
var range = model.getRange();
elements = $.map(model.annotations.getTimeLineAnnotatons(), function(ann, i) {
elements = model.annotations.getTimeLineAnnotatons().map(function(ann, i) {
return {
id: i,
id: ann.id,
start: ann.start_time || range.start,
end: ann.end_time || range.end,
content: ann.content,
content: ann.content || ann.title,
title: ann.title,
type: 'range'
};
Expand Down
63 changes: 53 additions & 10 deletions lib/core/time/maps.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,23 +175,66 @@ function filterVectorLayer(layer, range) {
if (timeAttr === undefined || l_features === undefined) {
return;
}
range = utils.createRange(range);
// loop over all original features and filter them
var features = [];
for (var i=0, ii=l_features.length; i<ii; ++i) {
var feature = l_features[i];
var featureTime = Date.parse(feature.get(timeAttr));
if (range.start != range.end) {
if (featureTime >= range.start && featureTime <= range.end) {
features.push(feature);
}
} else if (featureTime === range.start) {
features.push(feature);
visitAllLayerFeatureTimes(layer, function(f,r) {
if (range.intersects(r)) {
features.push(f);
} else {
}
}
});
layer.getSource().clear(true);
layer.getSource().addFeatures(features);
}

/**
* Call the provided visitor function on the specified features using the
* configuration provided in the layer. The visitor function will be called
* with the feature, and start and end time, if any. The features visited will
* be, in order of priority: the provided (optional) features argument, the
* layer property 'features', the layer's source features.
* @param {ol.layer.Layer} layer
* @param {function} visitor function(feature, start, end)
* @param {array} features (opitonal)
*/
function visitAllLayerFeatureTimes(layer, visitor, features) {
var startAtt = layer.get('timeAttribute');
var endAtt = layer.get('endTimeAttribute');
var rangeGetter;
features = features || layer.get('features') || layer.getSource().getFeatures();
if (endAtt) {
rangeGetter = function(f) {
var start = f.get(startAtt);
var end = f.get(endAtt);
return utils.createRange(start, end);
};
} else {
rangeGetter = function(f) {
var start = f.get(startAtt);
return utils.createRange(start, start);
};
}
utils.visitRanges(features, rangeGetter, visitor);
}

/**
* Compute the range of the provided features using the layer's configured
* timeattributes. If the optional features array is omitted, the features
* will come from the layer.
* @param {ol.layer.Layer} layer
* @param {array} features (optional)
* @returns {storytools.core.time.Range} range of features
*/
exports.computeVectorRange = function(layer, features) {
var startAtt = layer.get('timeAttribute');
var endAtt = layer.get('endTimeAttribute');
features = features || layer.get('features') || layer.getSource().getFeatures();
return utils.computeRange(features, function(f) {
return utils.createRange(f.get(startAtt), f.get(endAtt));
});
};

exports.filterVectorLayer = filterVectorLayer;

exports.MapController = function(options, timeControls) {
Expand Down
7 changes: 7 additions & 0 deletions lib/core/time/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ exports.TimeModel = function(options, boxes, annotations) {
if (opts.hasOwnProperty('mode') && opts.mode !== undefined) {
this.mode = opts.mode;
}
if (opts.hasOwnProperty('annotations')) {
this.annotations.update(opts.annotations);
}
// @todo is the best name for this
if (opts.hasOwnProperty('data')) {
boxModel.setRange(opts.data);
}
}

init.call(this, options);
Expand Down
Loading

0 comments on commit 0ad37ef

Please sign in to comment.