From 2c7f6b5dd892a4d52ef513cde735e7a12936ad4b Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Mon, 22 May 2017 13:41:57 +0200 Subject: [PATCH] searc by coordinates returns empty list if click is too far --- frontend-js/src/main/js/Functions.js | 35 ++++++++++ .../src/main/js/map/AbstractCustomMap.js | 2 +- frontend-js/src/main/js/map/CustomMap.js | 53 +++++++++++++++ .../main/js/map/overlay/AbstractDbOverlay.js | 6 +- .../main/js/map/overlay/SearchDbOverlay.js | 42 +++++++++--- .../test/js/gui/leftPanel/SearchPanel-test.js | 3 +- frontend-js/src/test/js/map/CustomMap-test.js | 63 ++++++++++-------- .../js/map/overlay/SearchDbOverlay-test.js | 66 +++++++++++++++++++ ...5781&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...5781&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...5781&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...5781&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...9172&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...9171&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...9171&projectId=sample&token=MOCK_TOKEN_ID& | 1 + ...3521&projectId=sample&token=MOCK_TOKEN_ID& | 1 + 16 files changed, 237 insertions(+), 41 deletions(-) create mode 100644 frontend-js/src/test/js/map/overlay/SearchDbOverlay-test.js create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=184.79,365.76&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=207.73,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=457.51,356.84&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=553.10,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329165,329172&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329171&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329171&projectId=sample&token=MOCK_TOKEN_ID& create mode 100644 frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153521&projectId=sample&token=MOCK_TOKEN_ID& diff --git a/frontend-js/src/main/js/Functions.js b/frontend-js/src/main/js/Functions.js index 1d32b43798..af62768bb7 100644 --- a/frontend-js/src/main/js/Functions.js +++ b/frontend-js/src/main/js/Functions.js @@ -1,5 +1,9 @@ "use strict"; +/* exported logger */ + +var logger = require('./logger'); + var Functions = {}; /** @@ -254,4 +258,35 @@ Functions.createElement = function(params) { return result; }; +function sqr(x) { + return x * x +} + +function dist2(v, w) { + return sqr(v.x - w.x) + sqr(v.y - w.y) +} + +function distToSegmentSquared(p, v, w) { + var l2 = dist2(v, w); + + if (l2 == 0) + return dist2(p, v); + + var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2; + + if (t < 0) + return dist2(p, v); + if (t > 1) + return dist2(p, w); + + return dist2(p, new google.maps.Point(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y))); +} +Functions.distance = function(p1, el2) { + if (el2 instanceof google.maps.Point) { + var p2 = el2; + return Math.sqrt((Math.pow(p1.x - p2.x, 2)) + (Math.pow(p1.y - p2.y, 2))) + } else { + return Math.sqrt(distToSegmentSquared(p1, el2.start, el2.end)); + } +}; module.exports = Functions; diff --git a/frontend-js/src/main/js/map/AbstractCustomMap.js b/frontend-js/src/main/js/map/AbstractCustomMap.js index 52a2150c14..f5d3fad9f2 100644 --- a/frontend-js/src/main/js/map/AbstractCustomMap.js +++ b/frontend-js/src/main/js/map/AbstractCustomMap.js @@ -317,7 +317,7 @@ AbstractCustomMap.prototype.registerMapClickEvents = function() { google.maps.event.addListener(this.getGoogleMap(), 'click', function(mouseEvent) { var point = self.fromLatLngToPoint(mouseEvent.latLng); var searchDb = customMap.getOverlayByName('search'); - return searchDb.searchByCoordinates({modelId: self.getModel().getId(), coordinates:point}).catch(GuiConnector.alert); + return searchDb.searchByCoordinates({modelId: self.getModel().getId(), coordinates:point, zoom: self.getGoogleMap().getZoom()}).catch(GuiConnector.alert); }); // select last clicked map diff --git a/frontend-js/src/main/js/map/CustomMap.js b/frontend-js/src/main/js/map/CustomMap.js index 71c01b9e11..bb5e7d4370 100644 --- a/frontend-js/src/main/js/map/CustomMap.js +++ b/frontend-js/src/main/js/map/CustomMap.js @@ -8,11 +8,13 @@ var Functions = require('../Functions'); var AbstractCustomMap = require('./AbstractCustomMap'); var AbstractDbOverlay = require('./overlay/AbstractDbOverlay'); var AliasMarker = require('./marker/AliasMarker'); +var Alias = require('./data/Alias'); var CommentDialog = require('../gui/CommentDialog'); var ControlType = require('./ControlType'); var CustomMapOptions = require('./CustomMapOptions'); var IdentifiedElement = require('./data/IdentifiedElement'); var PointMarker = require('./marker/PointMarker'); +var Reaction = require('./data/Reaction'); var ReactionMarker = require('./marker/ReactionMarker'); var ReactionOverlay = require('./overlay/ReactionOverlay'); var ReferenceGenome = require('./data/ReferenceGenome'); @@ -1243,6 +1245,57 @@ CustomMap.prototype.getSelectedPolygon = function() { return this._selectedPolygon; }; +CustomMap.prototype.getDistance = function(params) { + var self = this; + var ie = params.element; + var model = self.getSubmodelById(ie.getModelId()).getModel(); + if (ie.getModelId() !== params.modelId) { + throw new Error("Element and coordinates are on different maps: " + ie.getModelId() + ", " + params.modelId); + } + var x = params.coordinates.x; + var y = params.coordinates.y; + var p1 = new google.maps.Point(x, y); + return model.getByIdentifiedElement(ie).then(function(element) { + if (element instanceof Alias) { + if (element.getX() <= x && element.getX() + element.getWidth() >= x) { + if (element.getY() <= y && element.getY() + element.getHeight() >= y) { + return 0; + } else { + return Math.min( // + Math.abs(element.getY() - y), // + Math.abs(element.getY() + element.getHeight() - y) // + ); + } + } else if (element.getY() <= y && element.getY() + element.getHeight() >= y) { + return Math.min( // + Math.abs(element.getX() - x), // + Math.abs(element.getX() + element.getWidth() - x) // + ); + } else { + var elementX = element.getX(); + var elementY = element.getY(); + var elementWidth = element.getWidth(); + var elementHeight = element.getHeight(); + return Math.min( // + Functions.distance(p1, new google.maps.Point(elementX, y)), // + Functions.distance(p1, new google.maps.Point(elementX + elementWidth, elementY)), // + Functions.distance(p1, new google.maps.Point(elementX, elementY + elementHeight)), // + Functions.distance(p1, new google.maps.Point(elementX + elementWidth, elementY + elementHeight)) // + ); + } + } else if (element instanceof Reaction) { + var distance = Number.POSITIVE_INFINITY; + var lines = element.getLines(); + for (var i = 0; i < lines.length; i++) { + distance = Math.min(distance, Functions.distance(p1, lines[i])); + } + return distance; + } else { + throw new Error("Unknown element type: " + (typeof element)); + } + }); +}; + CustomMap.prototype.getSubmaps = function() { var submaps = this.submaps; if (submaps === undefined) { diff --git a/frontend-js/src/main/js/map/overlay/AbstractDbOverlay.js b/frontend-js/src/main/js/map/overlay/AbstractDbOverlay.js index 8f5b65bf1f..8e53a66fb5 100644 --- a/frontend-js/src/main/js/map/overlay/AbstractDbOverlay.js +++ b/frontend-js/src/main/js/map/overlay/AbstractDbOverlay.js @@ -45,14 +45,16 @@ AbstractDbOverlay.QueryType = { SEARCH_BY_QUERY : "SEARCH_BY_QUERY", }; -AbstractDbOverlay.prototype.encodeQuery = function(type, arg0, arg1) { +AbstractDbOverlay.prototype.encodeQuery = function(type, arg0, arg1, arg2) { if (type === AbstractDbOverlay.QueryType.SEARCH_BY_COORDINATES) { var modelId = arg0; var coordinates = arg1; + var zoom = arg2; return JSON.stringify({ type : type, modelId : modelId, - coordinates : coordinates + coordinates : coordinates, + zoom : arg2, }); } else if (type === AbstractDbOverlay.QueryType.SEARCH_BY_TARGET) { var target = arg0; diff --git a/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js index 15a8e3eb7c..5882419385 100644 --- a/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js +++ b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js @@ -66,9 +66,10 @@ SearchDbOverlay.prototype.getElementsByQuery = function(query) { SearchDbOverlay.prototype.searchByCoordinates = function(params) { var modelId = params.modelId; var coordinates = params.coordinates; - + var zoom = params.zoom; + var self = this; - var query = self.encodeQuery(AbstractDbOverlay.QueryType.SEARCH_BY_COORDINATES, modelId, coordinates); + var query = self.encodeQuery(AbstractDbOverlay.QueryType.SEARCH_BY_COORDINATES, modelId, coordinates, zoom); ServerConnector.getSessionData().setSearchQuery(query); @@ -79,25 +80,46 @@ SearchDbOverlay.prototype.searchByCoordinates = function(params) { Promise.resolve(self._elementsByQuery[query]); }); } else { - return ServerConnector.getClosestElementsByCoordinates({ - modelId : modelId, - coordinates : coordinates, - count : 1 + var searchResult = null; + var maxDistance; + + return ServerConnector.getMaxSearchDistance().then(function(distance) { + var maxZoom = self.getMap().getSubmodelById(modelId).getModel().getMaxZoom(); + var zoomDiff = maxZoom - zoom; + for (var i = 0; i < zoomDiff; i++) { + distance = distance * 1.5; + } + maxDistance = distance; + return ServerConnector.getClosestElementsByCoordinates({ + modelId : modelId, + coordinates : coordinates, + count : 1 + }); }).then(function(elements) { - self._elementsByQuery[query] = elements; + searchResult = elements; if (elements[0].getType() === "REACTION") { var model = self.getMap().getSubmodelById(elements[0].getModelId()).getModel(); return model.getReactionById(elements[0].getId(), true).then(function(reaction) { var i = 0; var reactionElements = reaction.getElements(); for (i = 0; i < reactionElements.length; i++) { - self._elementsByQuery[query].push(new IdentifiedElement(reactionElements[i])); + elements.push(new IdentifiedElement(reactionElements[i])); } }); - } else { - return null; } }).then(function() { + return self.getMap().getDistance({ + modelId : modelId, + coordinates : coordinates, + element : searchResult[0], + }); + }).then(function(distance) { + logger.debug(distance + ", " + maxDistance); + if (distance <= maxDistance) { + self._elementsByQuery[query] = searchResult; + } else { + self._elementsByQuery[query] = []; + } return self.callListeners('onSearch'); }).then(function() { return self._elementsByQuery[query]; diff --git a/frontend-js/src/test/js/gui/leftPanel/SearchPanel-test.js b/frontend-js/src/test/js/gui/leftPanel/SearchPanel-test.js index 483b7f4297..f16eeea600 100644 --- a/frontend-js/src/test/js/gui/leftPanel/SearchPanel-test.js +++ b/frontend-js/src/test/js/gui/leftPanel/SearchPanel-test.js @@ -42,7 +42,8 @@ describe('SearchPanel', function() { var searchParams = { modelId : map.getModel().getId(), - coordinates : new google.maps.Point(26547.33, 39419.29) + coordinates : new google.maps.Point(457.51, 356.84), + zoom : 4, }; return searchDbOverlay.searchByCoordinates(searchParams).then(function() { diff --git a/frontend-js/src/test/js/map/CustomMap-test.js b/frontend-js/src/test/js/map/CustomMap-test.js index 67e9d2cb3f..55b7a478bb 100644 --- a/frontend-js/src/test/js/map/CustomMap-test.js +++ b/frontend-js/src/test/js/map/CustomMap-test.js @@ -415,40 +415,49 @@ describe('CustomMap', function() { }); it("left click on map", function() { - var map = helper.createCustomMap(); - map.getModel().setId(15781); - - var searchOverlay = helper.createSearchDbOverlay(map); + var map; + var searchOverlay; + return ServerConnector.getProject().then(function(project) { + map = helper.createCustomMap(project); + searchOverlay = helper.createSearchDbOverlay(map); + + var mev = { + stop : null, + latLng : new google.maps.LatLng(82.32061703407554, -167.25586206896548), + }; + map.getGoogleMap().setZoom(4); + // latLng : new google.maps.LatLng(82.40238643645326, -148.44758620689652) + + assert.notOk(map.getActiveSubmapId()); + return google.maps.event.trigger(map.getGoogleMap(), 'click', mev); - var mev = { - stop : null, - latLng : new google.maps.LatLng(40.0, -90.0) - }; - - assert.notOk(map.getActiveSubmapId()); - return google.maps.event.trigger(map.getGoogleMap(), 'click', mev).then(function() { + }).then(function() { assert.equal(map.getId(), map.getActiveSubmapId()); - assert.ok(searchOverlay.aliasMarkers[329173]); + assert.ok(searchOverlay.aliasMarkers[329171]); }); + }); it("left click on reaction", function() { - var map = helper.createCustomMap(); - map.getModel().setId(15781); - - var searchOverlay = helper.createSearchDbOverlay(map); - - var mev = { - stop : null, - latLng : new google.maps.LatLng(42.0, -90.0) - }; - - assert.notOk(map.getActiveSubmapId()); - return google.maps.event.trigger(map.getGoogleMap(), 'click', mev).then(function() { + var map; + var searchOverlay; + return ServerConnector.getProject().then(function(project) { + map = helper.createCustomMap(project); + searchOverlay = helper.createSearchDbOverlay(map); + + var mev = { + stop : null, + latLng : new google.maps.LatLng(82.40238643645326, -148.44758620689652) + }; + map.getGoogleMap().setZoom(4); + + assert.notOk(map.getActiveSubmapId()); + return google.maps.event.trigger(map.getGoogleMap(), 'click', mev); + }).then(function() { assert.equal(map.getId(), map.getActiveSubmapId()); - assert.ok(searchOverlay.reactionMarkers[153515]); - assert.ok(searchOverlay.reactionMarkers[153515].isShown()); - assert.ok(searchOverlay.aliasMarkers[329162]); + assert.ok(searchOverlay.reactionMarkers[153521]); + assert.ok(searchOverlay.reactionMarkers[153521].isShown()); + assert.ok(searchOverlay.aliasMarkers[329165]); }); }); diff --git a/frontend-js/src/test/js/map/overlay/SearchDbOverlay-test.js b/frontend-js/src/test/js/map/overlay/SearchDbOverlay-test.js new file mode 100644 index 0000000000..c208703820 --- /dev/null +++ b/frontend-js/src/test/js/map/overlay/SearchDbOverlay-test.js @@ -0,0 +1,66 @@ +"use strict"; + +var Helper = require('../../Helper'); + +var logger = require('../../logger'); + +var IdentifiedElement = require('../../../../main/js/map/data/IdentifiedElement'); +var SearchDbOverlay = require('../../../../main/js/map/overlay/SearchDbOverlay'); + +var assert = require('assert'); + +describe('SearchDbOverlay', function() { + var helper; + before(function() { + helper = new Helper(); + }); + + it("constructor 1", function() { + var map = helper.createCustomMap(); + var oc = new SearchDbOverlay({ + map : map, + name : 'search' + }); + assert.ok(oc); + assert.equal(oc.getName(), 'search'); + + assert.equal(logger.getWarnings.length, 0); + }); + + it("searchByCoordinates with too far alias as result", function() { + return ServerConnector.getProject().then(function(project) { + var map = helper.createCustomMap(project); + map.getModel().setId(15781); + var searchDb = helper.createSearchDbOverlay(map); + + var searchParams = { + modelId : map.getModel().getId(), + coordinates : new google.maps.Point(207.73, 479.18), + zoom : 4, + }; + return searchDb.searchByCoordinates(searchParams); + }).then(function(result) { + assert.equal(result.length, 0); + }); + + }); + + it("searchByCoordinates with too far reaction as result", function() { + return ServerConnector.getProject().then(function(project) { + var map = helper.createCustomMap(project); + map.getModel().setId(15781); + var searchDb = helper.createSearchDbOverlay(map); + + var searchParams = { + modelId : map.getModel().getId(), + coordinates : new google.maps.Point(553.10,479.18), + zoom : 4, + }; + return searchDb.searchByCoordinates(searchParams); + }).then(function(result) { + assert.equal(result.length, 0); + }); + + }); + +}); diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=184.79,365.76&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=184.79,365.76&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..3ab3e25631 --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=184.79,365.76&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"id":329171,"type":"ALIAS"}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=207.73,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=207.73,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..3ab3e25631 --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=207.73,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"id":329171,"type":"ALIAS"}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=457.51,356.84&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=457.51,356.84&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..8916c4e02e --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=457.51,356.84&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"id":153521,"type":"REACTION"}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=553.10,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=553.10,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..8916c4e02e --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getClosestElementsByCoordinates/coordinates=553.10,479.18&count=1&modelId=15781&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"id":153521,"type":"REACTION"}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329165,329172&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329165,329172&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..f488c10208 --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329165,329172&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Phenotype","name":"s7","bounds":{"x":213.0,"y":128.0,"width":80.0,"height":30.0},"id":329172},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Ion","name":"s8","bounds":{"x":358.5,"y":125.5,"width":25.0,"height":25.0},"id":329165},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Complex","name":"s12","bounds":{"x":271.0,"y":207.0,"width":101.0,"height":164.0},"id":329158}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329171&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329171&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..956ac7a6af --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329171&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"bounds":{"x":160.0,"y":332.0,"width":119.0,"height":63.0},"id":329171}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329171&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329171&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..956ac7a6af --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=id,bounds,modelId&id=329171&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"bounds":{"x":160.0,"y":332.0,"width":119.0,"height":63.0},"id":329171}] \ No newline at end of file diff --git a/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153521&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153521&projectId=sample&token=MOCK_TOKEN_ID& new file mode 100644 index 0000000000..715ec32565 --- /dev/null +++ b/frontend-js/testFiles/apiCalls/project/getReactions/columns=&id=153521&projectId=sample&token=MOCK_TOKEN_ID& @@ -0,0 +1 @@ +[{"modelId":15781,"reactants":"329158","reactionId":"re23","id":153521,"type":"Dissociation","lines":[{"start":{"x":371.99999999999994,"y":315.10161915826575},"end":{"x":411.64153465957406,"y":335.590891248036},"type":"START"},{"start":{"x":508.05348418531685,"y":279.95891320505365},"end":{"x":379.6820939222325,"y":146.9928441065957},"type":"END"},{"start":{"x":543.7499999999999,"y":362.874999999999},"end":{"x":508.05348418531685,"y":279.95891320505365},"type":"MIDDLE"},{"start":{"x":449.8434737629195,"y":307.88463901503604},"end":{"x":270.9073813308621,"y":158.0},"type":"END"},{"start":{"x":543.7499999999999,"y":362.874999999999},"end":{"x":546.6153123135401,"y":307.94923690478544},"type":"MIDDLE"},{"start":{"x":546.6153123135401,"y":307.94923690478544},"end":{"x":449.8434737629195,"y":307.88463901503604},"type":"MIDDLE"},{"start":{"x":543.7499999999999,"y":362.874999999999},"end":{"x":500.90979517443395,"y":356.00742559541567},"type":"MIDDLE"},{"start":{"x":500.90979517443395,"y":356.00742559541567},"end":{"x":458.3899053124841,"y":359.75342920945576},"type":"MIDDLE"},{"start":{"x":458.3899053124841,"y":359.75342920945576},"end":{"x":418.7483706529099,"y":339.2641571196856},"type":"MIDDLE"}],"modifiers":"","centerPoint":{"x":415.194952656242,"y":337.4275241838608},"products":"329165,329172"}] \ No newline at end of file -- GitLab